Setup

library(tidyverse)
library(pals)
library(tidygraph)
library(igraph)
library(qgraph)
library(ggraph)
library(ggnewscale)
library(ggpubr)

knitr::opts_chunk$set(python.reticulate = FALSE)
setwd('~/repos/global-resistome/')

# Load various custom functions used in this notebook
source('src/network_functions.R')

# Load lists of variables used for plotting and other tasks.
source('src/corr_defaults.R')

Load data

Host label to host groups

count.data <- read_csv('data/resistome_uc90_v2.csv')

# query for meta data
# select * from Meta_public;
meta.data <- read_tsv('data/metadata.tsv', na = c("NULL", "")) %>%
  mutate(
    host = case_when(
      is.na(host) ~ 'Mising',
      TRUE ~ host
    )
  ) %>%
  left_join(enframe(host2group), by=c("host" = "name")) %>%
  rename("hostGroup" = "value")
  

all.data <-   meta.data  %>% 
  inner_join(count.data, by=c("run_accession")) %>%
  select(c(run_accession, host, hostGroup, year, latitude, longitude, country, instrument_platform, raw_reads, refSequence, fragmentCountAln, trimmed_fragments))
host.gene.counts <- all.data %>%
  group_by(hostGroup, refSequence) %>%
  summarise(fragmentCountAln=sum(fragmentCountAln))
`summarise()` has grouped output by 'hostGroup'. You can override using the `.groups` argument.
all.gene.counts <- all.data %>%
  group_by(refSequence) %>%
  summarise(fragmentCountAln=sum(fragmentCountAln)) %>%
  mutate(hostGroup = 'all') %>%
  select(hostGroup, refSequence, fragmentCountAln)

trimmedFrag.hosts <- rbind(
  all.data %>%
  distinct(hostGroup, run_accession, host, trimmed_fragments) %>%
  group_by(hostGroup) %>%
  summarise(trimmed_fragments=sum(trimmed_fragments)), 
  all.data %>%
  distinct(hostGroup, run_accession, host, trimmed_fragments) %>%
  summarise(trimmed_fragments=sum(trimmed_fragments)) %>%
  mutate(hostGroup='all')) %>%
  filter(!is.na(hostGroup))

gene.counts <- rbind(all.gene.counts, host.gene.counts) %>%
  left_join(trimmedFrag.hosts, by=c("hostGroup")) %>%
  mutate(FPKM = fragmentCountAln / (trimmed_fragments / 1e6))
resClasses <- get_resClasses()
Loading required package: RMariaDB
Error: Failed to connect: Can't connect to MySQL server on '10.25.0.22' (60)

Make network files


makeFiles <- T
if (makeFiles) {
  corrFiles <- list.files(path='data/res_minF50_minS10', pattern=glob2rx("*_corr.npy"), full.names = T)
  pvalFiles <- list.files(path='data/res_minF50_minS10', pattern=glob2rx("*_pval.npy"), full.names = T)
  colFiles <- list.files(path='data/res_minF50_minS10', pattern=glob2rx("*_columns.list"), full.names = T)
  
  assertthat::assert_that(all(length(corrFiles) > 0,c(length(corrFiles) == length(pvalFiles), length(corrFiles) == length(colFiles), length(pvalFiles) == length(colFiles))))
  alpha = 0.01
  min_corr = 0.6
  
  all.matrices <- list()
  for (i in 1:length(corrFiles)) {
    mat <- dataLoader(
        corrFile=corrFiles[i],
        pvalFile=pvalFiles[i],
        colFile=colFiles[i]
    )
    
    host <- get_host(str_replace(corrFiles[i], '_sparr_corr.npy', ''))
    all.matrices <- c(all.matrices, setNames(list(mat), host))
    
    prefix = str_replace(basename(corrFiles[i]), '_sparr_corr.npy', '')
    graphFile = file.path('output/networks_sparcc_frag50_minn10', paste0(prefix, '_', min_corr))
    print(paste("Writing network json file to", graphFile))
    graph <- make_net(mat, resClasses, alpha, min_corr, graphFile)
  }
}
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_air_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_all_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_canis_lupus_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_chicken_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_cow_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_freshwater_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_homo_sapiens_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_marine_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_mouse_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_pig_0.6"
[1] "Writing network json file to output/networks_sparcc_frag50_minn10/sparcc_soil_0.6"
# Grab names for all the different network files and load them
netFiles <- list.files(path='/Users/hanmar/repos/global-resistome2/output/networks_sparcc_frag50_minn10', pattern = glob2rx('sparcc_*_0.6.json'), full.names = T)

networks <- list()
for (netFile in netFiles) {
  host <- get_host(netFile)
  if (!host %in% hosts) {
    next
  }
  
  # load graph file
  G <- importGraph(netFile) %>%
    activate(edges) %>%
    mutate(weight=corr) %>%
    select(-c(Var1_class, Var2_class)) %>%
    activate(nodes) %>%
    select(-ResFinder_class) %>%
    left_join(resClasses, by=c("name")) 
  
  networks <- c(networks, setNames(list(G), host))
}

Abundance visualizations (Figure 1)

get_host_order <- function(x){return(which(x == hosts))}

sum.gene.counts <- gene.counts %>%
  filter(!is.na(hostGroup)) %>%
  left_join(resClasses, by=c("refSequence"="name")) %>%
  group_by(hostGroup, ResFinder_class) %>%
  summarise(fragmentCountAln=sum(fragmentCountAln), nGeneClass = n_distinct(refSequence)) #%>%
`summarise()` has grouped output by 'hostGroup'. You can override using the `.groups` argument.
  #pivot_wider(id_cols=hostGroup, names_from=ResFinder_class, values_from=fragmentCountAln) 


sum.gene.counts2 <- sum.gene.counts %>%
  group_by(hostGroup) %>%
  summarise(sumVar=sum(fragmentCountAln), nGene=sum(nGeneClass)) %>%
  left_join(sum.gene.counts, by=c("hostGroup")) %>%
  mutate(
    fragClosed = (100 / sumVar) * fragmentCountAln,
    hostOrder = mapply(get_host_order, hostGroup),
    hostGroup2 = str_to_title(case_when(
      hostGroup == 'canis_lupus' ~ 'Dog',
      hostGroup == 'homo_sapiens' ~ 'Human',
      TRUE ~ hostGroup))
  )

p.class.rels <- ggplot(sum.gene.counts2) +
  geom_bar(aes(y=reorder(hostGroup2, hostOrder), x=fragClosed, fill=ResFinder_class), stat='identity') +
  scale_fill_manual('ResFinder class',values = classes_colors) +
  xlab('Relative abundance') +
  ylab('Source') +
  theme_light() +
  theme(legend.position="bottom")  +
  scale_x_continuous(breaks=seq(0, 100, 10)) +
  guides(fill=guide_legend(nrow=5))


# Genes with most fragments in the various environmental and host sources
gene.rels <- gene.counts %>%
  filter(!is.na(hostGroup)) %>%
  group_by(hostGroup) %>%
  summarise(sumVar=sum(fragmentCountAln)) %>%
  left_join(gene.counts, by=c("hostGroup")) %>% 
  mutate(
    fragClo = (100 / sumVar) * fragmentCountAln
  )

gene.rels.top10 <- gene.rels %>%
  group_by(hostGroup) %>%
  top_n(wt=fragClo, n=10) %>%
  left_join(resClasses, by=c("refSequence"="name")) %>%
  mutate(
    refSequence = sub('_[^_]+$', '', refSequence),
    hostOrder = mapply(get_host_order, hostGroup),
    hostGroup2 = str_to_title(case_when(
      hostGroup == 'canis_lupus' ~ 'Dog',
      hostGroup == 'homo_sapiens' ~ 'Human',
      TRUE ~ hostGroup))
  )

p.gene.rels <- ggplot(gene.rels.top10) +
  geom_point(aes(y=reorder(hostGroup2, hostOrder), x=fragClo, color=ResFinder_class), alpha=.5) +
  ggforce::facet_zoom(x = fragClo < 15, zoom.size=1, show.area=F) +
  ggrepel::geom_text_repel(aes(x=fragClo, y=hostGroup, color=ResFinder_class, label=refSequence), size=2, box.padding = unit(.25, "lines"), max.overlaps = 10) +
  scale_color_manual(values = classes_colors) +
  xlab('Relative abundance') +
  ylab('Source') +
  guides(color='none') +
  scale_x_continuous(breaks=seq(0, 100, 10)) +
  theme_light()

(p.rels.combined <- ggarrange(p.class.rels, p.gene.rels, ncol = 1, nrow=2, labels = 'auto', heights = c(0.75, 1)))
ggsave('output/relative_abundances.pdf', width=15, height=15)
ggsave('output/relative_abundances.png', width=15, height=15)
ggsave('output/relative_abundances.eps', width=15, height=15)

Metadata overview

Define colors for the various sampling sources

host_colors_palette <- tibble(host=c(hosts, 'other'), hostOrder=1:12, color=c(pals::tol(n=11), 'grey')) %>%
  mutate(
    hostGroup = str_to_title(case_when(
      host == 'canis_lupus' ~ 'Dog',
      host == 'homo_sapiens' ~ 'Human',
      TRUE ~ host))
)
host_colors = setNames(host_colors_palette$color, host_colors_palette$hostGroup)
host_col_scale <- scale_colour_manual(name = "Sampling source", values = host_colors, limits = force)
host_fill_scale <- scale_fill_manual(name = "Sampling source", values = host_colors, limits = force)

Sampling year (Figure S1)

meta.data %>%
  mutate(year=replace_na(year, 'Unknown'), hostGroup=replace_na(hostGroup, 'other')) %>%
  group_by(hostGroup, year) %>%
  summarise(n_runs=n_distinct(run_accession)) %>%
  ungroup() %>%
  mutate(
    hostOrder = replace_na(mapply(get_host_order, hostGroup), length(hosts)+1),
    hostGroup = str_to_title(case_when(
      hostGroup == 'canis_lupus' ~ 'Dog',
      hostGroup == 'homo_sapiens' ~ 'Human',
      TRUE ~ hostGroup))
  ) %>%
  ggplot() +
  geom_col(aes(y=year, x=n_runs, fill=reorder(hostGroup, hostOrder))) +
  host_fill_scale +
  ylim(seq(2000, 2020, 1), 'Unknown') +
  xlab('Sample count') +
  ylab('Sampling year') +
  theme_light()
`summarise()` has grouped output by 'hostGroup'. You can override using the `.groups` argument.
Error: object 'host_fill_scale' not found

Sequencing platforms (Figure S2)

meta.data %>%
  mutate(hostGroup = str_to_title(case_when(
      hostGroup == 'canis_lupus' ~ 'Dog',
      hostGroup == 'homo_sapiens' ~ 'Human',
      is.na(hostGroup) ~ 'Other',
      TRUE ~ hostGroup))) %>%
  ggplot() +
  geom_col(aes(x=raw_reads, y=instrument_platform, fill=hostGroup)) + 
  host_fill_scale +
  facet_wrap(instrument_platform ~ ., scales='free', ncol=1) +
  theme_light() +
  theme(strip.text = element_blank(), strip.background = element_blank()) +
  xlab('Count of raw sequencing reads') + 
  ylab('Instrument platform') 
ggsave('figs/run_platform.png', width=7, height=5)
ggsave('figs/run_platform.pdf', width=7, height=5)
ggsave('figs/run_platform.eps', width=7, height=5)

Sampling locations (Figure S3)

library(maps)

Attaching package: ‘maps’

The following object is masked from ‘package:purrr’:

    map
map <- map_data('world')

gps.runs.host <- meta.data %>%
  filter(!is.na(hostGroup)) %>%
  group_by(latitude, longitude, hostGroup) %>%
  summarise(n_runs=n_distinct(run_accession))
`summarise()` has grouped output by 'latitude', 'longitude'. You can override using the `.groups` argument.
gps.runs <- meta.data %>%
  group_by(latitude, longitude) %>%
  summarise(n_runs=n_distinct(run_accession)) %>%
  mutate(hostGroup = 'all')
`summarise()` has grouped output by 'latitude'. You can override using the `.groups` argument.
gps.runs.combi <- rbind(gps.runs, gps.runs.host) %>%
  mutate(
    hostOrder = mapply(get_host_order, hostGroup),
    hostGroup = str_to_title(case_when(
      hostGroup == 'canis_lupus' ~ 'Dog',
      hostGroup == 'homo_sapiens' ~ 'Human',
      TRUE ~ hostGroup))
  )

p.map.runs <- ggplot() +
  geom_polygon(data=map, aes(x=long, y = lat, group = group), fill="grey", alpha=0.3) +
  geom_point( data=gps.runs.combi , aes(x=longitude, y=latitude, size=n_runs), color="black", alpha=.4) + # %>% ungroup() %>% sample_n(100)
  facet_wrap(fct_reorder(hostGroup, hostOrder) ~ ., ncol=3) +
  theme_void() +
  theme(strip.text.x = element_text(size=14)) +
  scale_size('Number of samples', range=c(.5, 5)) 

ggsave(plot=p.map.runs, filename='figs/run_coordinates.png', width=20, height=15)
ggsave(plot=p.map.runs, filename='figs/run_coordinates.pdf', width=20, height=15)
ggsave(plot=p.map.runs, filename='figs/run_coordinates.eps', width=20, height=15)

rbind(
  meta.data %>%
    filter(is.na(latitude), is.na(longitude))%>%
    summarise(n=n_distinct(run_accession)) %>%
    mutate(hostGroup='all'),
  meta.data %>%
    group_by(hostGroup) %>%
    filter(is.na(latitude), is.na(longitude))%>%
    summarise(n=n_distinct(run_accession))
)

Edge distributions (Figure S4)

all.p.edges <- list()
for (host in hosts) {
  
  hostMat <- all.matrices[[host]]
  if (host == 'canis_lupus') {
    host = 'dog'
  } else if (host == 'homo_sapiens') {
    host = 'human'
  }
  h <- str_to_title(host)
  
  p.edges <- hostMat %>%
    mutate(
      Selected = case_when(
        pd < alpha & corr >= min_corr ~ TRUE,
        TRUE ~ FALSE
      )
    ) %>%
  ggplot() +
    geom_point(aes(x=corr, y=pd, color=Selected), alpha=.5, size=.5) +
    scale_color_manual(values=c("FALSE" = 'red', "TRUE" = 'blue')) +
    theme_light() +
    ylab('One-tailed p-value') +
    xlab('Correlation') +
    ggtitle(h)
  
   all.p.edges <- c(all.p.edges, setNames(list(p.edges), host))
}

p.all.edges <- ggarrange(plotlist = all.p.edges, common.legend = T, ncol = 3, nrow=4)
ggsave(plot = p.all.edges, filename = 'output/networks_sparcc_frag50_minn10/all_edges_dist.png', width=10, height=10)
ggsave(plot = p.all.edges, filename = 'output/networks_sparcc_frag50_minn10/all_edges_dist.pdf', width=10, height=10)
ggsave(plot = p.all.edges, filename = 'output/networks_sparcc_frag50_minn10/all_edges_dist.eps', width=10, height=10)

Network visualizations (Figure 2a)

Each network is visualized by using the Fruchterman-Reingold layout and save as a pdf/png/tiff.

source.network.plots <- list()
all.network <- NULL
for (host in names(networks)) {

  G <- networks[[host]]  

  if(host == 'homo_sapiens') {
    host = 'human'
  } else if (host == 'canis_lupus') {
    host = 'dog'
  }
  plottitle <- str_to_title(str_replace(host, '_', ' '))
  
  
  # define layout
  e <- get.edgelist(G, names=F)
  layout <- qgraph.layout.fruchtermanreingold(e, vcount = vcount(G),
                                              area = 6*(vcount(G)^2), repulse.rad = vcount(G)^2.5)

  #  make visualizations with and without legends
  graph.plot.leg <- ggraph(G, layout="manual", x=layout[,1], y=layout[,2]) +
    geom_edge_link0(aes(color=corr, width=corr, alpha=corr)) +
    geom_node_point(aes(color=ResFinder_class)) +
    scale_edge_color_viridis("Correlation",direction=-1, limits=c(0.6,1)) +
    scale_edge_width(range=c(.2, 1), guide="none") +
    scale_edge_alpha(range=c(0.5, 0.9), guide="none") +
    scale_color_manual("ResFinder class", values=classes_colors) +
    # scale_shape_manual("Classified\nas CIA", values=c('0' = 15, '1'=17)) +
    ggtitle(plottitle) +
    theme_graph()
  
  ggsave(plot=graph.plot.leg, filename=paste0('figs/network_', host, '.png'), width=15, height=10)



  graph.plot.noleg <- ggraph(G, layout="manual", x=layout[,1], y=layout[,2]) +
    geom_edge_link0(aes(color=corr, width=corr, alpha=corr)) +
    geom_node_point(aes(color=ResFinder_class)) +
    scale_edge_color_viridis(direction=-1, guide="none", limits=c(0.6, 1)) +
    scale_edge_width(range=c(.1, 1), guide="none") +
    scale_edge_alpha(range=c(0.5, 0.9), guide="none") +
    scale_color_manual(values=classes_colors, guide="none") +
    ggtitle(plottitle) +
    theme_graph(title_size = 14)
  ggsave(plot=graph.plot.noleg, filename=paste0('figs/network_', host, '_noleg.png'), width=10, height=10)
  
  graph.plot.noleg.cia <- ggraph(G, layout="manual", x=layout[,1], y=layout[,2]) +
    geom_edge_link0(aes(color=corr, width=corr, alpha=corr)) +
    geom_node_point(aes(color=ResFinder_class)) +
    scale_edge_color_viridis(direction=-1, limits=c(0.6,1), guide="none") +
    scale_edge_width(range=c(.2, 1), guide="none") +
    scale_edge_alpha(range=c(0.5, 0.9), guide="none") +
    scale_color_manual(values=classes_colors, guide="none") +
    # scale_shape_manual(values=c('0' = 15, '1'=17), guide="none") +
    ggtitle(plottitle) +
    theme_graph()
  ggsave(plot=graph.plot.noleg.cia, filename=paste0('figs/network_', host, '_noleg_cia.png'), width=10, height=10)

  
  # with arg names on it
  graph.plot.anno <- G %>%
    activate(nodes) %>%
    mutate(name2 = sub('_[^_]+$', '', name)) %>%
   ggraph(layout="manual", x=layout[,1], y=layout[,2]) +
    geom_edge_link0(aes(color=corr, width=corr, alpha=corr)) +
    geom_node_point(aes(color=ResFinder_class)) +
    geom_node_text(aes(label=name2), size=2, repel = T, check_overlap = T) +
    scale_edge_color_viridis("Correlation",direction=-1, limits=c(0.6,1)) +
    scale_edge_width(range=c(.2, 1), guide="none") +
    scale_edge_alpha(range=c(0.7, 0.9), guide="none") +
    scale_color_manual("ResFinder class", values=classes_colors) +
    ggtitle(plottitle) +
    theme_graph(title_size = 14)
  ggsave(plot=graph.plot.anno, filename=paste0('figs/network_', host, '_anno.png'), width=15, height=10)

  if(host == 'all') {
    all.network <- graph.plot.leg
  } else {
    source.network.plots <- c(source.network.plots, setNames(list(graph.plot.noleg.cia), host))
  }
}

p.sources <- ggarrange(plotlist=source.network.plots[order(names(source.network.plots))], nrow=2, ncol=ceiling(length(source.network.plots)/2), common.legend = T)
p.combined <- ggarrange(all.network, p.sources, ncol=2, common.legend = T, legend='bottom', widths=c(.5, 1), labels='a')
ggsave(plot=p.combined, filename='figs/network_combined.png', width=22, height=8)

Network characteristics (Figure 2b)

Summarise global properties of the different networks

ggsave('figs/network_metrics.png')
Saving 7 x 7 in image
ggsave('figs/network_metrics.png')
ggsave('figs/network_metrics.pdf')

Distribution of correlation coefficients / weights (Figure 2c)

all.edges <- tibble()
for (host in names(networks)) {
  
  G <- networks[[host]] 
  
  host_order = which(host == hosts)
  
  edge.data <- G %>%
    as_data_frame %>%
    select(from, to, corr) %>%
    mutate(host=host, host_order=host_order)
  all.edges <- rbind(all.edges, edge.data)
}


all.edges <- all.edges %>%
  mutate(
    host = str_to_title(case_when(
      host == 'canis_lupus' ~ 'Dog',
      host == 'homo_sapiens' ~ 'Human',
      TRUE ~ host
    ))
  )

(p.edge.dist <- ggplot(all.edges) +
  geom_histogram(aes(x=corr), color='white', binwidth = 0.01) +
  facet_grid(fct_reorder(host, host_order)~., scales='free_y') +
  xlab('Correlation') + ylab('Count') + 
  #theme_pubr() 
  theme_light() +
  theme(axis.text.y = element_text(size=8)) 
)

NA

Overlapping nodes and edges in the networks (Figure 2d)

overlapping.edges <- matrix(nrow=length(networks), ncol=length(networks))
overlapping.nodes <- matrix(nrow=length(networks), ncol=length(networks))

colnames(overlapping.edges) <- rownames(overlapping.edges) <- names(networks)
colnames(overlapping.nodes) <- rownames(overlapping.nodes) <- names(networks)

for (hostSet in utils::combn(names(networks), m=2, simplify = F)) {
  h1 = hostSet[1]
  h2 = hostSet[2]
  
  G1 <- networks[[h1]]
  G2 <- networks[[h2]]
  
  if (any(is.null(G1), is.null(G2))) {
    next
  }
  
  overlapping.edges[h1, h2] <- length(intersect(E(G1), E(G2)))
  overlapping.nodes[h1, h2] <- length( intersect(V(G1), V(G2)))

}

overlapping.edges <- as_tibble(overlapping.edges,rownames = 'h1') %>%
  pivot_longer(-h1, names_to = 'h2')
overlapping.nodes <- as_tibble(overlapping.nodes,rownames = 'h1') %>%
  pivot_longer(-h1, names_to = 'h2') 

(p.overlaps <- ggplot() +
  geom_tile(data=overlapping.nodes, aes(x=h1, y=h2, fill=value)) +
  geom_text(data=overlapping.nodes, aes(x=h1, y=h2, label=value), color='white') +
  scale_fill_viridis_c(str_wrap("Overlapping nodes", 11), na.value=NA) +
  new_scale_fill() +
  geom_tile(data=overlapping.edges, aes(y=h1, x=h2, fill=value)) +
  geom_text(data=overlapping.edges, aes(y=h1, x=h2, label=value), color='white') +
  scale_fill_viridis_c(str_wrap("Overlapping edges", 11), na.value=NA, option='H') +
  scale_x_discrete(labels=c('Air', 'All', 'Dog', 'Chicken', 'Cow', 'Freshwater', 'Human', 'Marine', 'Mouse', 'Pig', 'Soil')) +
  scale_y_discrete(labels=c('Air', 'All', 'Dog', 'Chicken', 'Cow', 'Freshwater', 'Human', 'Marine', 'Mouse', 'Pig', 'Soil')) +
  theme_classic() +
  theme(
    axis.title = element_blank(),
    legend.margin=margin(0,0,0,0)
  )
)

ggsave('figs/network_overlaps.png')
Saving 7.29 x 4.51 in image
ggsave('figs/network_overlaps.pdf', width=10, height=5)

combine

p.net_descs <- ggarrange(p.netchars, p.edge.dist, p.overlaps, ncol=3, labels=c('b', 'c', 'd'))
Warning: Removed 66 rows containing missing values (geom_text).Warning: Removed 66 rows containing missing values (geom_text).
ggsave(plot=p.net_descs, filename='figs/network_characteristics.png', width=20, height=6)

Local properties of nodes (Figure 3)

looking at the nodes with the highest degrees

node.centralities <- tibble()
for (host in names(networks)) {
  
  G <- networks[[host]]
  
  node.cen <- G %>%
    activate(nodes) %>%
    mutate(
      degree = centrality_degree(weights=corr),
      betweenness = centrality_betweenness(weights=corr)
    ) %>%
    as_tibble() %>%
    filter(ResFinder_class != 'Missing') %>%
    #top_n(10, wt=degree) %>%
    mutate(
      host=host
    ) %>%
    select(-c(x, y, pagerank, nodedegree, id))
  
  node.centralities <- rbind(node.centralities, node.cen)
}

col_scale <- scale_colour_manual(name = "ResFinder class", values = classes_colors,
                                 limits = force)

node.centralities %>%
  group_by(host) %>%
  top_n(10, wt=degree) %>%
  mutate(name2 = sub('_[^_]+$', '', name)) %>%
  left_join(gene.rels, by=c("name" = "refSequence", "host" = "hostGroup"))  %>%
  mutate(
    host = str_to_title(case_when(
      host == 'canis_lupus' ~ 'Dog',
      host == 'homo_sapiens' ~ 'Human',
      TRUE ~ host
    ))
  ) %>%
  ggplot() +
  geom_point(aes(x=host, y=name2, color=ResFinder_class, size=fragClo, alpha=degree))  +
  scale_alpha_continuous("Number of edges", range=c(0.25, 0.95), trans='log', breaks=c(1, 10, 25, 50)) +
  scale_size_continuous("Relative abundance", range=c(2, 8), breaks=c(5, 10, 25, 50, 75, 100)) +
  col_scale +
  theme_light() +
  #  theme(
  #  legend.position = 'bottom', 
  #  legend.box="vertical"
  #) +
  guides(
    color=guide_legend(byrow = F, ncol=1, title.position="top")
    #size=guide_legend(title.position="top")
  ) +
  xlab("") + ylab("") +
  ggtitle("ARGs with 10 highest number of edges per network")


ggsave('figs/degree_top10_network_abundances_rel.png', width = 15, height=10)
ggsave('figs/degree_top10_network_abundances_rel.pdf', width = 15, height=10)
ggsave('figs/degree_top10_network_abundances_rel.eps', width = 15, height=10)

Selected genes in networks (Figure S5)

gene.regexes <- c("catA1", "mef\\(A\\)_1", "tet\\(L\\)_4")
#all.gene.graphplots <- list()
gene.graphplots <- list()
for (gene.regex in gene.regexes) {
  
  add_legend = length(gene.regexes) == which(gene.regex == gene.regexes)
  for (host in hosts) {
    G <- networks[[host]]
    G.sel <- extract_neighborhood(gene.regex, G)
    if (is.null(G.sel)) {
      p.h <- ggplot() + ggtitle(str_to_title(host)) + theme_void() + theme_graph(base_family="sans") + annotate("text",label="No correlations", x=0, y=0)
    } else {
      
      p.h <- G.sel %>%
        activate(nodes) %>%
        left_join(filter(gene.rels, hostGroup == host), by=c("name" = "refSequence")) %>%
        mutate(name=sub('_[^_]+$', '', name)) %>%
        ggraph(layout="nicely") +
        geom_edge_link0(aes(color=corr, width=corr, alpha=corr)) +
        geom_node_point(aes(color=ResFinder_class, size=fragClo, shape=sel))  +
        geom_node_label(aes(label=name), size=2, repel = T) +
        scale_edge_width(range=c(.2, 1), guide="none") +
        scale_edge_alpha(range=c(0.5, 0.9), guide="none") +
        scale_edge_color_viridis("Correlation",direction=-1,limits=c(0.6, 1)) +
        scale_size_continuous('Relative abundance', range=c(1, 4),  breaks=c(5, 10, 25, 50, 75, 100)) +
        scale_color_manual("ResFinder class", values=classes_colors) +
        scale_shape_manual("Highlighted", values=list("Yes" = 16, "No" = 15))+
        ggtitle(str_to_title(host)) +
        theme_graph(base_family="sans") 
      
      if(!add_legend){
        p.h <- p.h + guides(colour="none", shape="none", size="none", edge_color="none")
      }
      # if (add_legend) {
      #   p.h <- p.h + 
      #     scale_color_manual("ResFinder class", values=classes_colors) +
      #     scale_edge_color_viridis("Correlation",direction=-1,limits=c(0.6, 1)) +
      #     scale_size('Relative abundance', range=c(1, 4)) +
      #     scale_shape_manual("Highlighted", values=list("Yes" = 16, "No" = 15))  
      # 
      # } else {
      #   p.h <- p.h + 
      #     scale_color_manual(guide="none", values=classes_colors) +
      #     scale_edge_color_viridis(guide="none",direction=-1,limits=c(0.6, 1)) +
      #     scale_shape_manual(guide="none", values=list("Yes" = 16, "No" = 15))  +
      #     scale_size(guide="none", range=c(1, 4)) 

      # }
    }
      gene.graphplots <- c(gene.graphplots, list(p.h))
  }
  #print(paste(gene.regex, length(gene.graphplots)))
  #p.g <- ggarrange(plotlist=gene.graphplots, nrow=1, common.legend = T, legend="bottom")
  #all.gene.graphplots <- c(all.gene.graphplots, list(p.g))
}

p.gp.all <- ggarrange(
  plotlist=gene.graphplots,
  ncol=11, nrow=length(gene.regexes), 
  common.legend = T, 
  labels = c("a. catA1_1", rep("", 10), "b. mef(A)_1", rep("", 10), "c. tet(L)_4", rep("", 10)), 
  font.label = list(size = 18, color = "black", face = "bold", family = NULL)
)

ggsave(plot=p.gp.all, filename = "figs/degree_top10_network_sel.png", width=30,height=15)
ggsave(plot=p.gp.all, filename = "figs/degree_top10_network_sel.eps", width=30,height=15, family="sans")
ggsave(plot=p.gp.all, filename = "figs/degree_top10_network_sel.pdf", width=30,height=15)
# ggsave does not want to create pdf, so let's use convert
# system("convert output/networks_sparcc_frag50_minn10/degree_top10_network_sel.png output/networks_sparcc_frag50_minn10/degree_top10_network_sel.pdf")

Number of nodes and connections to the different classes (Figure 4)

classDegreeSums <- tibble()

for (host in names(networks)) {
  G <- networks[[host]]
  
  class.degree.sums <- G %>% 
    activate(nodes) %>% 
    as_tibble() %>% 
    group_by(ResFinder_class) %>%
    summarise(sum_degree = sum(nodedegree), n_nodes=n_distinct(name)) %>%
    mutate(host=str_to_title(str_replace(host, '_', ' ')))
  
  classDegreeSums <- rbind(classDegreeSums, class.degree.sums)
}


classDegreeSums %>%
  ggplot() +
  geom_point(aes(x=n_nodes, y=sum_degree, color=ResFinder_class, shape=host)) +
  scale_shape_manual("Network", values=c(1:11)) +
  scale_color_manual("ResFinder class", values=classes_colors)  +
  xlab('Number of nodes (genes)') +
  ylab('Number of edges (correlations)') +
  theme_light() +
  theme(legend.position = "bottom", legend.box = "vertical", legend.direction = "horizontal") +
  guides(color = guide_legend(ncol = 2, byrow=F)) 
ggsave('figs/count_class_edges.png', width=10, height=10)
ggsave('figs/count_class_edges.pdf', width=10, height=10)
ggsave('figs/count_class_edges.eps', width=10, height=10)
# p + opts(legend.direction = "horizontal", legend.position = "bottom", legend.box = "vertical")

Averaging class-class correlations (Figure S6)

How: with Fisher’s z-transformation.

class2classCorrs <- tibble()
for (host in names(networks)) {
  
  G <- networks[[host]]
  ho <- which(host == hosts)
  
  G.data <- as_data_frame(G) %>%
    left_join(select(as_data_frame(G, what='vertices'), c(name, ResFinder_class)), by=c("from" = "name")) %>%
    rename(Var1_class = ResFinder_class)%>%
    left_join(select(as_data_frame(G, what='vertices'), c(name, ResFinder_class)), by=c("to" = "name")) %>%
    rename(Var2_class = ResFinder_class)
  
  avg.corr <- G.data %>%
    mutate(zscore = DescTools::FisherZ(corr)) %>%
    group_by(From=pmin(Var1_class, Var2_class), To=pmax(Var1_class, Var2_class)) %>%
    summarise(meanzscore = mean(zscore), n_edges = n()) %>%
    mutate(avgcorr = DescTools::FisherZInv(meanzscore), host=host, host_order=ho) 
  
  class2classCorrs <- rbind(class2classCorrs, avg.corr)
}
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'From'. You can override using the `.groups` argument.
class2classCorrs2 <- class2classCorrs %>%
  unite(From.To, From, To, sep="-", remove=F) %>%
  group_by(host) %>%
  mutate(pct_edges = n_edges / sum(n_edges) * 100) %>%
  mutate(
    host = str_to_title(case_when(
      host == 'canis_lupus' ~ 'Dog',
      host == 'homo_sapiens' ~ 'Human',
      TRUE ~ host
    ))
)

plot.avgs.corrs <- ggplot(class2classCorrs2) +
  geom_point(aes(x=host, y=From.To, size=pct_edges, color=avgcorr)) +
  scale_color_viridis("Average\ncorrelation",direction=-1, limits=c(0.6,1)) +
  scale_size("Percentage\nof edges") +
  theme_light() +
  theme(axis.text.y = element_blank(), axis.title = element_blank())

axis.color.cons <- ggplot(class2classCorrs2) +
  geom_tile(aes(x="C1", y=From.To, fill=From)) +
  geom_tile(aes(x="C2", y=From.To, fill=To)) +
  scale_fill_manual("ResFinder class",values=classes_colors) +
  guides(fill=guide_legend(ncol = 4)) +
  theme_light() +
  theme(
    axis.text.y = element_blank(), 
    axis.title = element_blank(),
    axis.ticks.y = element_blank()
  )

ggarrange(
  ggarrange(axis.color.cons, plot.avgs.corrs, ncol=2, widths=c(0.055, 1), common.legend = T),
  as_ggplot(get_legend(plot.avgs.corrs)), 
  ncol=2,
  widths = c(1, 0.1)
)

#ggsave(
#  filename = 'output/networks_sparcc_frag50_minn10/network_avg_corrs.pdf',
#  width=16, height=15
#)
#system("convert output/networks_sparcc_frag50_minn10/network_avg_corrs.pdf output/networks_sparcc_frag50_minn10/network_avg_corrs.png")
LS0tCnRpdGxlOiAiU3BhckNDIGNvcnJlbGF0aW9ucyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQojIFNldHVwIApgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXI9bm9ybWFsaXplUGF0aCgnfi9yZXBvcy9nbG9iYWwtcmVzaXN0b21lMicpKQpgYGAKCmBgYHtyIHByZWFtYmxlLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGFscykKbGlicmFyeSh0aWR5Z3JhcGgpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KHFncmFwaCkKbGlicmFyeShnZ3JhcGgpCmxpYnJhcnkoZ2duZXdzY2FsZSkKbGlicmFyeShnZ3B1YnIpCgprbml0cjo6b3B0c19jaHVuayRzZXQocHl0aG9uLnJldGljdWxhdGUgPSBGQUxTRSkKc2V0d2QoJ34vcmVwb3MvZ2xvYmFsLXJlc2lzdG9tZS8nKQoKIyBMb2FkIHZhcmlvdXMgY3VzdG9tIGZ1bmN0aW9ucyB1c2VkIGluIHRoaXMgbm90ZWJvb2sKc291cmNlKCdzcmMvbmV0d29ya19mdW5jdGlvbnMuUicpCgojIExvYWQgbGlzdHMgb2YgdmFyaWFibGVzIHVzZWQgZm9yIHBsb3R0aW5nIGFuZCBvdGhlciB0YXNrcy4Kc291cmNlKCdzcmMvY29ycl9kZWZhdWx0cy5SJykKYGBgCgojIExvYWQgZGF0YQpIb3N0IGxhYmVsIHRvIGhvc3QgZ3JvdXBzCmBgYHtyIGxvYWRfY291bnRfZGF0YX0KY291bnQuZGF0YSA8LSByZWFkX2NzdignZGF0YS9yZXNpc3RvbWVfdWM5MF92Mi5jc3YnKQoKIyBxdWVyeSBmb3IgbWV0YSBkYXRhCiMgc2VsZWN0ICogZnJvbSBNZXRhX3B1YmxpYzsKbWV0YS5kYXRhIDwtIHJlYWRfdHN2KCdkYXRhL21ldGFkYXRhLnRzdicsIG5hID0gYygiTlVMTCIsICIiKSkgJT4lCiAgbXV0YXRlKAogICAgaG9zdCA9IGNhc2Vfd2hlbigKICAgICAgaXMubmEoaG9zdCkgfiAnTWlzaW5nJywKICAgICAgVFJVRSB+IGhvc3QKICAgICkKICApICU+JQogIGxlZnRfam9pbihlbmZyYW1lKGhvc3QyZ3JvdXApLCBieT1jKCJob3N0IiA9ICJuYW1lIikpICU+JQogIHJlbmFtZSgiaG9zdEdyb3VwIiA9ICJ2YWx1ZSIpCiAgCgphbGwuZGF0YSA8LSAgIG1ldGEuZGF0YSAgJT4lIAogIGlubmVyX2pvaW4oY291bnQuZGF0YSwgYnk9YygicnVuX2FjY2Vzc2lvbiIpKSAlPiUKICBzZWxlY3QoYyhydW5fYWNjZXNzaW9uLCBob3N0LCBob3N0R3JvdXAsIHllYXIsIGxhdGl0dWRlLCBsb25naXR1ZGUsIGNvdW50cnksIGluc3RydW1lbnRfcGxhdGZvcm0sIHJhd19yZWFkcywgcmVmU2VxdWVuY2UsIGZyYWdtZW50Q291bnRBbG4sIHRyaW1tZWRfZnJhZ21lbnRzKSkKYGBgCgpgYGB7ciBtYW5pcHVsYXRlX2NvdW50X2RhdGF9Cmhvc3QuZ2VuZS5jb3VudHMgPC0gYWxsLmRhdGEgJT4lCiAgZ3JvdXBfYnkoaG9zdEdyb3VwLCByZWZTZXF1ZW5jZSkgJT4lCiAgc3VtbWFyaXNlKGZyYWdtZW50Q291bnRBbG49c3VtKGZyYWdtZW50Q291bnRBbG4pKQoKYWxsLmdlbmUuY291bnRzIDwtIGFsbC5kYXRhICU+JQogIGdyb3VwX2J5KHJlZlNlcXVlbmNlKSAlPiUKICBzdW1tYXJpc2UoZnJhZ21lbnRDb3VudEFsbj1zdW0oZnJhZ21lbnRDb3VudEFsbikpICU+JQogIG11dGF0ZShob3N0R3JvdXAgPSAnYWxsJykgJT4lCiAgc2VsZWN0KGhvc3RHcm91cCwgcmVmU2VxdWVuY2UsIGZyYWdtZW50Q291bnRBbG4pCgp0cmltbWVkRnJhZy5ob3N0cyA8LSByYmluZCgKICBhbGwuZGF0YSAlPiUKICBkaXN0aW5jdChob3N0R3JvdXAsIHJ1bl9hY2Nlc3Npb24sIGhvc3QsIHRyaW1tZWRfZnJhZ21lbnRzKSAlPiUKICBncm91cF9ieShob3N0R3JvdXApICU+JQogIHN1bW1hcmlzZSh0cmltbWVkX2ZyYWdtZW50cz1zdW0odHJpbW1lZF9mcmFnbWVudHMpKSwgCiAgYWxsLmRhdGEgJT4lCiAgZGlzdGluY3QoaG9zdEdyb3VwLCBydW5fYWNjZXNzaW9uLCBob3N0LCB0cmltbWVkX2ZyYWdtZW50cykgJT4lCiAgc3VtbWFyaXNlKHRyaW1tZWRfZnJhZ21lbnRzPXN1bSh0cmltbWVkX2ZyYWdtZW50cykpICU+JQogIG11dGF0ZShob3N0R3JvdXA9J2FsbCcpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGhvc3RHcm91cCkpCgpnZW5lLmNvdW50cyA8LSByYmluZChhbGwuZ2VuZS5jb3VudHMsIGhvc3QuZ2VuZS5jb3VudHMpICU+JQogIGxlZnRfam9pbih0cmltbWVkRnJhZy5ob3N0cywgYnk9YygiaG9zdEdyb3VwIikpICU+JQogIG11dGF0ZShGUEtNID0gZnJhZ21lbnRDb3VudEFsbiAvICh0cmltbWVkX2ZyYWdtZW50cyAvIDFlNikpCmBgYAoKCgpgYGB7ciBsb2FkX2dlbmVfYW5ub3N9CnJlc0NsYXNzZXMgPC0gZ2V0X3Jlc0NsYXNzZXMoKQpgYGAKCk1ha2UgbmV0d29yayBmaWxlcwpgYGB7ciBtYWtlX25ldHdvcmtfZmlsZXN9CgptYWtlRmlsZXMgPC0gVAppZiAobWFrZUZpbGVzKSB7CiAgY29yckZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aD0nZGF0YS9yZXNfbWluRjUwX21pblMxMCcsIHBhdHRlcm49Z2xvYjJyeCgiKl9jb3JyLm5weSIpLCBmdWxsLm5hbWVzID0gVCkKICBwdmFsRmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoPSdkYXRhL3Jlc19taW5GNTBfbWluUzEwJywgcGF0dGVybj1nbG9iMnJ4KCIqX3B2YWwubnB5IiksIGZ1bGwubmFtZXMgPSBUKQogIGNvbEZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aD0nZGF0YS9yZXNfbWluRjUwX21pblMxMCcsIHBhdHRlcm49Z2xvYjJyeCgiKl9jb2x1bW5zLmxpc3QiKSwgZnVsbC5uYW1lcyA9IFQpCiAgCiAgYXNzZXJ0dGhhdDo6YXNzZXJ0X3RoYXQoYWxsKGxlbmd0aChjb3JyRmlsZXMpID4gMCxjKGxlbmd0aChjb3JyRmlsZXMpID09IGxlbmd0aChwdmFsRmlsZXMpLCBsZW5ndGgoY29yckZpbGVzKSA9PSBsZW5ndGgoY29sRmlsZXMpLCBsZW5ndGgocHZhbEZpbGVzKSA9PSBsZW5ndGgoY29sRmlsZXMpKSkpCiAgYWxwaGEgPSAwLjAxCiAgbWluX2NvcnIgPSAwLjYKICAKICBhbGwubWF0cmljZXMgPC0gbGlzdCgpCiAgZm9yIChpIGluIDE6bGVuZ3RoKGNvcnJGaWxlcykpIHsKICAgIG1hdCA8LSBkYXRhTG9hZGVyKAogICAgICAgIGNvcnJGaWxlPWNvcnJGaWxlc1tpXSwKICAgICAgICBwdmFsRmlsZT1wdmFsRmlsZXNbaV0sCiAgICAgICAgY29sRmlsZT1jb2xGaWxlc1tpXQogICAgKQogICAgCiAgICBob3N0IDwtIGdldF9ob3N0KHN0cl9yZXBsYWNlKGNvcnJGaWxlc1tpXSwgJ19zcGFycl9jb3JyLm5weScsICcnKSkKICAgIGFsbC5tYXRyaWNlcyA8LSBjKGFsbC5tYXRyaWNlcywgc2V0TmFtZXMobGlzdChtYXQpLCBob3N0KSkKICAgIAogICAgcHJlZml4ID0gc3RyX3JlcGxhY2UoYmFzZW5hbWUoY29yckZpbGVzW2ldKSwgJ19zcGFycl9jb3JyLm5weScsICcnKQogICAgZ3JhcGhGaWxlID0gZmlsZS5wYXRoKCdvdXRwdXQvbmV0d29ya3Nfc3BhcmNjX2ZyYWc1MF9taW5uMTAnLCBwYXN0ZTAocHJlZml4LCAnXycsIG1pbl9jb3JyKSkKICAgIHByaW50KHBhc3RlKCJXcml0aW5nIG5ldHdvcmsganNvbiBmaWxlIHRvIiwgZ3JhcGhGaWxlKSkKICAgIGdyYXBoIDwtIG1ha2VfbmV0KG1hdCwgcmVzQ2xhc3NlcywgYWxwaGEsIG1pbl9jb3JyLCBncmFwaEZpbGUpCiAgfQp9CgojIEdyYWIgbmFtZXMgZm9yIGFsbCB0aGUgZGlmZmVyZW50IG5ldHdvcmsgZmlsZXMgYW5kIGxvYWQgdGhlbQpuZXRGaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGg9Jy9Vc2Vycy9oYW5tYXIvcmVwb3MvZ2xvYmFsLXJlc2lzdG9tZTIvb3V0cHV0L25ldHdvcmtzX3NwYXJjY19mcmFnNTBfbWlubjEwJywgcGF0dGVybiA9IGdsb2IycngoJ3NwYXJjY18qXzAuNi5qc29uJyksIGZ1bGwubmFtZXMgPSBUKQoKbmV0d29ya3MgPC0gbGlzdCgpCmZvciAobmV0RmlsZSBpbiBuZXRGaWxlcykgewogIGhvc3QgPC0gZ2V0X2hvc3QobmV0RmlsZSkKICBpZiAoIWhvc3QgJWluJSBob3N0cykgewogICAgbmV4dAogIH0KICAKICAjIGxvYWQgZ3JhcGggZmlsZQogIEcgPC0gaW1wb3J0R3JhcGgobmV0RmlsZSkgJT4lCiAgICBhY3RpdmF0ZShlZGdlcykgJT4lCiAgICBtdXRhdGUod2VpZ2h0PWNvcnIpICU+JQogICAgc2VsZWN0KC1jKFZhcjFfY2xhc3MsIFZhcjJfY2xhc3MpKSAlPiUKICAgIGFjdGl2YXRlKG5vZGVzKSAlPiUKICAgIHNlbGVjdCgtUmVzRmluZGVyX2NsYXNzKSAlPiUKICAgIGxlZnRfam9pbihyZXNDbGFzc2VzLCBieT1jKCJuYW1lIikpIAogIAogIG5ldHdvcmtzIDwtIGMobmV0d29ya3MsIHNldE5hbWVzKGxpc3QoRyksIGhvc3QpKQp9CmBgYAoKIyBBYnVuZGFuY2UgdmlzdWFsaXphdGlvbnMgKEZpZ3VyZSAxKQpgYGB7ciByZWxhdGl2ZV9hYnVuZGFuY2VzfQpnZXRfaG9zdF9vcmRlciA8LSBmdW5jdGlvbih4KXtyZXR1cm4od2hpY2goeCA9PSBob3N0cykpfQoKc3VtLmdlbmUuY291bnRzIDwtIGdlbmUuY291bnRzICU+JQogIGZpbHRlcighaXMubmEoaG9zdEdyb3VwKSkgJT4lCiAgbGVmdF9qb2luKHJlc0NsYXNzZXMsIGJ5PWMoInJlZlNlcXVlbmNlIj0ibmFtZSIpKSAlPiUKICBncm91cF9ieShob3N0R3JvdXAsIFJlc0ZpbmRlcl9jbGFzcykgJT4lCiAgc3VtbWFyaXNlKGZyYWdtZW50Q291bnRBbG49c3VtKGZyYWdtZW50Q291bnRBbG4pLCBuR2VuZUNsYXNzID0gbl9kaXN0aW5jdChyZWZTZXF1ZW5jZSkpICMlPiUKICAjcGl2b3Rfd2lkZXIoaWRfY29scz1ob3N0R3JvdXAsIG5hbWVzX2Zyb209UmVzRmluZGVyX2NsYXNzLCB2YWx1ZXNfZnJvbT1mcmFnbWVudENvdW50QWxuKSAKCgpzdW0uZ2VuZS5jb3VudHMyIDwtIHN1bS5nZW5lLmNvdW50cyAlPiUKICBncm91cF9ieShob3N0R3JvdXApICU+JQogIHN1bW1hcmlzZShzdW1WYXI9c3VtKGZyYWdtZW50Q291bnRBbG4pLCBuR2VuZT1zdW0obkdlbmVDbGFzcykpICU+JQogIGxlZnRfam9pbihzdW0uZ2VuZS5jb3VudHMsIGJ5PWMoImhvc3RHcm91cCIpKSAlPiUKICBtdXRhdGUoCiAgICBmcmFnQ2xvc2VkID0gKDEwMCAvIHN1bVZhcikgKiBmcmFnbWVudENvdW50QWxuLAogICAgaG9zdE9yZGVyID0gbWFwcGx5KGdldF9ob3N0X29yZGVyLCBob3N0R3JvdXApLAogICAgaG9zdEdyb3VwMiA9IHN0cl90b190aXRsZShjYXNlX3doZW4oCiAgICAgIGhvc3RHcm91cCA9PSAnY2FuaXNfbHVwdXMnIH4gJ0RvZycsCiAgICAgIGhvc3RHcm91cCA9PSAnaG9tb19zYXBpZW5zJyB+ICdIdW1hbicsCiAgICAgIFRSVUUgfiBob3N0R3JvdXApKQogICkKCnAuY2xhc3MucmVscyA8LSBnZ3Bsb3Qoc3VtLmdlbmUuY291bnRzMikgKwogIGdlb21fYmFyKGFlcyh5PXJlb3JkZXIoaG9zdEdyb3VwMiwgaG9zdE9yZGVyKSwgeD1mcmFnQ2xvc2VkLCBmaWxsPVJlc0ZpbmRlcl9jbGFzcyksIHN0YXQ9J2lkZW50aXR5JykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCdSZXNGaW5kZXIgY2xhc3MnLHZhbHVlcyA9IGNsYXNzZXNfY29sb3JzKSArCiAgeGxhYignUmVsYXRpdmUgYWJ1bmRhbmNlJykgKwogIHlsYWIoJ1NvdXJjZScpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwgMTAwLCAxMCkpICsKICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQobnJvdz01KSkKCgojIEdlbmVzIHdpdGggbW9zdCBmcmFnbWVudHMgaW4gdGhlIHZhcmlvdXMgZW52aXJvbm1lbnRhbCBhbmQgaG9zdCBzb3VyY2VzCmdlbmUucmVscyA8LSBnZW5lLmNvdW50cyAlPiUKICBmaWx0ZXIoIWlzLm5hKGhvc3RHcm91cCkpICU+JQogIGdyb3VwX2J5KGhvc3RHcm91cCkgJT4lCiAgc3VtbWFyaXNlKHN1bVZhcj1zdW0oZnJhZ21lbnRDb3VudEFsbikpICU+JQogIGxlZnRfam9pbihnZW5lLmNvdW50cywgYnk9YygiaG9zdEdyb3VwIikpICU+JSAKICBtdXRhdGUoCiAgICBmcmFnQ2xvID0gKDEwMCAvIHN1bVZhcikgKiBmcmFnbWVudENvdW50QWxuCiAgKQoKZ2VuZS5yZWxzLnRvcDEwIDwtIGdlbmUucmVscyAlPiUKICBncm91cF9ieShob3N0R3JvdXApICU+JQogIHRvcF9uKHd0PWZyYWdDbG8sIG49MTApICU+JQogIGxlZnRfam9pbihyZXNDbGFzc2VzLCBieT1jKCJyZWZTZXF1ZW5jZSI9Im5hbWUiKSkgJT4lCiAgbXV0YXRlKAogICAgcmVmU2VxdWVuY2UgPSBzdWIoJ19bXl9dKyQnLCAnJywgcmVmU2VxdWVuY2UpLAogICAgaG9zdE9yZGVyID0gbWFwcGx5KGdldF9ob3N0X29yZGVyLCBob3N0R3JvdXApLAogICAgaG9zdEdyb3VwMiA9IHN0cl90b190aXRsZShjYXNlX3doZW4oCiAgICAgIGhvc3RHcm91cCA9PSAnY2FuaXNfbHVwdXMnIH4gJ0RvZycsCiAgICAgIGhvc3RHcm91cCA9PSAnaG9tb19zYXBpZW5zJyB+ICdIdW1hbicsCiAgICAgIFRSVUUgfiBob3N0R3JvdXApKQogICkKCnAuZ2VuZS5yZWxzIDwtIGdncGxvdChnZW5lLnJlbHMudG9wMTApICsKICBnZW9tX3BvaW50KGFlcyh5PXJlb3JkZXIoaG9zdEdyb3VwMiwgaG9zdE9yZGVyKSwgeD1mcmFnQ2xvLCBjb2xvcj1SZXNGaW5kZXJfY2xhc3MpLCBhbHBoYT0uNSkgKwogIGdnZm9yY2U6OmZhY2V0X3pvb20oeCA9IGZyYWdDbG8gPCAxNSwgem9vbS5zaXplPTEsIHNob3cuYXJlYT1GKSArCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKGFlcyh4PWZyYWdDbG8sIHk9aG9zdEdyb3VwLCBjb2xvcj1SZXNGaW5kZXJfY2xhc3MsIGxhYmVsPXJlZlNlcXVlbmNlKSwgc2l6ZT0yLCBib3gucGFkZGluZyA9IHVuaXQoLjI1LCAibGluZXMiKSwgbWF4Lm92ZXJsYXBzID0gMTApICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY2xhc3Nlc19jb2xvcnMpICsKICB4bGFiKCdSZWxhdGl2ZSBhYnVuZGFuY2UnKSArCiAgeWxhYignU291cmNlJykgKwogIGd1aWRlcyhjb2xvcj0nbm9uZScpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLCAxMDAsIDEwKSkgKwogIHRoZW1lX2xpZ2h0KCkKCihwLnJlbHMuY29tYmluZWQgPC0gZ2dhcnJhbmdlKHAuY2xhc3MucmVscywgcC5nZW5lLnJlbHMsIG5jb2wgPSAxLCBucm93PTIsIGxhYmVscyA9ICdhdXRvJywgaGVpZ2h0cyA9IGMoMC43NSwgMSkpKQpnZ3NhdmUoJ2ZpZ3MvcmVsYXRpdmVfYWJ1bmRhbmNlcy5wZGYnLCB3aWR0aD0xNSwgaGVpZ2h0PTE1KQpnZ3NhdmUoJ2ZpZ3MvcmVsYXRpdmVfYWJ1bmRhbmNlcy5wbmcnLCB3aWR0aD0xNSwgaGVpZ2h0PTE1KQpnZ3NhdmUoJ2ZpZ3MvcmVsYXRpdmVfYWJ1bmRhbmNlcy5lcHMnLCB3aWR0aD0xNSwgaGVpZ2h0PTE1KQoKYGBgCiMgTWV0YWRhdGEgb3ZlcnZpZXcKRGVmaW5lIGNvbG9ycyBmb3IgdGhlIHZhcmlvdXMgc2FtcGxpbmcgc291cmNlcwpgYGB7ciBtZXRhZGF0YV9wYWxldHRlc30KaG9zdF9jb2xvcnNfcGFsZXR0ZSA8LSB0aWJibGUoaG9zdD1jKGhvc3RzLCAnb3RoZXInKSwgaG9zdE9yZGVyPTE6MTIsIGNvbG9yPWMocGFsczo6dG9sKG49MTEpLCAnZ3JleScpKSAlPiUKICBtdXRhdGUoCiAgICBob3N0R3JvdXAgPSBzdHJfdG9fdGl0bGUoY2FzZV93aGVuKAogICAgICBob3N0ID09ICdjYW5pc19sdXB1cycgfiAnRG9nJywKICAgICAgaG9zdCA9PSAnaG9tb19zYXBpZW5zJyB+ICdIdW1hbicsCiAgICAgIFRSVUUgfiBob3N0KSkKKQpob3N0X2NvbG9ycyA9IHNldE5hbWVzKGhvc3RfY29sb3JzX3BhbGV0dGUkY29sb3IsIGhvc3RfY29sb3JzX3BhbGV0dGUkaG9zdEdyb3VwKQpob3N0X2NvbF9zY2FsZSA8LSBzY2FsZV9jb2xvdXJfbWFudWFsKG5hbWUgPSAiU2FtcGxpbmcgc291cmNlIiwgdmFsdWVzID0gaG9zdF9jb2xvcnMsIGxpbWl0cyA9IGZvcmNlKQpob3N0X2ZpbGxfc2NhbGUgPC0gc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICJTYW1wbGluZyBzb3VyY2UiLCB2YWx1ZXMgPSBob3N0X2NvbG9ycywgbGltaXRzID0gZm9yY2UpCgpgYGAKIyMgU2FtcGxpbmcgeWVhciAoRmlndXJlIFMxKQpgYGB7ciBwbG90X21ldGFkYXRhX3llYXJzfQptZXRhLmRhdGEgJT4lCiAgbXV0YXRlKHllYXI9cmVwbGFjZV9uYSh5ZWFyLCAnVW5rbm93bicpLCBob3N0R3JvdXA9cmVwbGFjZV9uYShob3N0R3JvdXAsICdvdGhlcicpKSAlPiUKICBncm91cF9ieShob3N0R3JvdXAsIHllYXIpICU+JQogIHN1bW1hcmlzZShuX3J1bnM9bl9kaXN0aW5jdChydW5fYWNjZXNzaW9uKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZSgKICAgIGhvc3RPcmRlciA9IHJlcGxhY2VfbmEobWFwcGx5KGdldF9ob3N0X29yZGVyLCBob3N0R3JvdXApLCBsZW5ndGgoaG9zdHMpKzEpLAogICAgaG9zdEdyb3VwID0gc3RyX3RvX3RpdGxlKGNhc2Vfd2hlbigKICAgICAgaG9zdEdyb3VwID09ICdjYW5pc19sdXB1cycgfiAnRG9nJywKICAgICAgaG9zdEdyb3VwID09ICdob21vX3NhcGllbnMnIH4gJ0h1bWFuJywKICAgICAgVFJVRSB+IGhvc3RHcm91cCkpCiAgKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9jb2woYWVzKHk9eWVhciwgeD1uX3J1bnMsIGZpbGw9cmVvcmRlcihob3N0R3JvdXAsIGhvc3RPcmRlcikpKSArCiAgaG9zdF9maWxsX3NjYWxlICsKICB5bGltKHNlcSgyMDAwLCAyMDIwLCAxKSwgJ1Vua25vd24nKSArCiAgeGxhYignU2FtcGxlIGNvdW50JykgKwogIHlsYWIoJ1NhbXBsaW5nIHllYXInKSArCiAgdGhlbWVfbGlnaHQoKQpnZ3NhdmUoJ2ZpZ3MvcnVuX3llYXJzLnBuZycsIHdpZHRoPTcsIGhlaWdodD01KQpnZ3NhdmUoJ2ZpZ3MvcnVuX3llYXJzLnBkZicsIHdpZHRoPTcsIGhlaWdodD01KQpnZ3NhdmUoJ2ZpZ3MvcnVuX3llYXJzLmVwcycsIHdpZHRoPTcsIGhlaWdodD01KQoKbWV0YS5kYXRhICU+JQogIGZpbHRlcihpcy5uYSh5ZWFyKSkgJT4lCiAgZGltKCkKYGBgCiMjIFNlcXVlbmNpbmcgcGxhdGZvcm1zIChGaWd1cmUgUzIpCmBgYHtyIHBsb3RfbWV0YWRhdGFfcGxhdGZvcm1zfQptZXRhLmRhdGEgJT4lCiAgbXV0YXRlKGhvc3RHcm91cCA9IHN0cl90b190aXRsZShjYXNlX3doZW4oCiAgICAgIGhvc3RHcm91cCA9PSAnY2FuaXNfbHVwdXMnIH4gJ0RvZycsCiAgICAgIGhvc3RHcm91cCA9PSAnaG9tb19zYXBpZW5zJyB+ICdIdW1hbicsCiAgICAgIGlzLm5hKGhvc3RHcm91cCkgfiAnT3RoZXInLAogICAgICBUUlVFIH4gaG9zdEdyb3VwKSkpICU+JQogIGdncGxvdCgpICsKICBnZW9tX2NvbChhZXMoeD1yYXdfcmVhZHMsIHk9aW5zdHJ1bWVudF9wbGF0Zm9ybSwgZmlsbD1ob3N0R3JvdXApKSArIAogIGhvc3RfZmlsbF9zY2FsZSArCiAgZmFjZXRfd3JhcChpbnN0cnVtZW50X3BsYXRmb3JtIH4gLiwgc2NhbGVzPSdmcmVlJywgbmNvbD0xKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHhsYWIoJ0NvdW50IG9mIHJhdyBzZXF1ZW5jaW5nIHJlYWRzJykgKyAKICB5bGFiKCdJbnN0cnVtZW50IHBsYXRmb3JtJykgCmdnc2F2ZSgnZmlncy9ydW5fcGxhdGZvcm0ucG5nJywgd2lkdGg9NywgaGVpZ2h0PTUpCmdnc2F2ZSgnZmlncy9ydW5fcGxhdGZvcm0ucGRmJywgd2lkdGg9NywgaGVpZ2h0PTUpCmdnc2F2ZSgnZmlncy9ydW5fcGxhdGZvcm0uZXBzJywgd2lkdGg9NywgaGVpZ2h0PTUpCmBgYAoKIyMgU2FtcGxpbmcgbG9jYXRpb25zIChGaWd1cmUgUzMpCmBgYHtyIHBsb3RfbWV0YWRhdGFfbG9jYXRpb25zfQpsaWJyYXJ5KG1hcHMpCm1hcCA8LSBtYXBfZGF0YSgnd29ybGQnKQoKZ3BzLnJ1bnMuaG9zdCA8LSBtZXRhLmRhdGEgJT4lCiAgZmlsdGVyKCFpcy5uYShob3N0R3JvdXApKSAlPiUKICBncm91cF9ieShsYXRpdHVkZSwgbG9uZ2l0dWRlLCBob3N0R3JvdXApICU+JQogIHN1bW1hcmlzZShuX3J1bnM9bl9kaXN0aW5jdChydW5fYWNjZXNzaW9uKSkKCmdwcy5ydW5zIDwtIG1ldGEuZGF0YSAlPiUKICBncm91cF9ieShsYXRpdHVkZSwgbG9uZ2l0dWRlKSAlPiUKICBzdW1tYXJpc2Uobl9ydW5zPW5fZGlzdGluY3QocnVuX2FjY2Vzc2lvbikpICU+JQogIG11dGF0ZShob3N0R3JvdXAgPSAnYWxsJykKCmdwcy5ydW5zLmNvbWJpIDwtIHJiaW5kKGdwcy5ydW5zLCBncHMucnVucy5ob3N0KSAlPiUKICBtdXRhdGUoCiAgICBob3N0T3JkZXIgPSBtYXBwbHkoZ2V0X2hvc3Rfb3JkZXIsIGhvc3RHcm91cCksCiAgICBob3N0R3JvdXAgPSBzdHJfdG9fdGl0bGUoY2FzZV93aGVuKAogICAgICBob3N0R3JvdXAgPT0gJ2NhbmlzX2x1cHVzJyB+ICdEb2cnLAogICAgICBob3N0R3JvdXAgPT0gJ2hvbW9fc2FwaWVucycgfiAnSHVtYW4nLAogICAgICBUUlVFIH4gaG9zdEdyb3VwKSkKICApCgpwLm1hcC5ydW5zIDwtIGdncGxvdCgpICsKICBnZW9tX3BvbHlnb24oZGF0YT1tYXAsIGFlcyh4PWxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLCBmaWxsPSJncmV5IiwgYWxwaGE9MC4zKSArCiAgZ2VvbV9wb2ludCggZGF0YT1ncHMucnVucy5jb21iaSAsIGFlcyh4PWxvbmdpdHVkZSwgeT1sYXRpdHVkZSwgc2l6ZT1uX3J1bnMpLCBjb2xvcj0iYmxhY2siLCBhbHBoYT0uNCkgKyAjICU+JSB1bmdyb3VwKCkgJT4lIHNhbXBsZV9uKDEwMCkKICBmYWNldF93cmFwKGZjdF9yZW9yZGVyKGhvc3RHcm91cCwgaG9zdE9yZGVyKSB+IC4sIG5jb2w9MykgKwogIHRoZW1lX3ZvaWQoKSArCiAgdGhlbWUoc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemU9MTQpKSArCiAgc2NhbGVfc2l6ZSgnTnVtYmVyIG9mIHNhbXBsZXMnLCByYW5nZT1jKC41LCA1KSkgCgpnZ3NhdmUocGxvdD1wLm1hcC5ydW5zLCBmaWxlbmFtZT0nZmlncy9ydW5fY29vcmRpbmF0ZXMucG5nJywgd2lkdGg9MjAsIGhlaWdodD0xNSkKZ2dzYXZlKHBsb3Q9cC5tYXAucnVucywgZmlsZW5hbWU9J2ZpZ3MvcnVuX2Nvb3JkaW5hdGVzLnBkZicsIHdpZHRoPTIwLCBoZWlnaHQ9MTUpCmdnc2F2ZShwbG90PXAubWFwLnJ1bnMsIGZpbGVuYW1lPSdmaWdzL3J1bl9jb29yZGluYXRlcy5lcHMnLCB3aWR0aD0yMCwgaGVpZ2h0PTE1KQoKcmJpbmQoCiAgbWV0YS5kYXRhICU+JQogICAgZmlsdGVyKGlzLm5hKGxhdGl0dWRlKSwgaXMubmEobG9uZ2l0dWRlKSklPiUKICAgIHN1bW1hcmlzZShuPW5fZGlzdGluY3QocnVuX2FjY2Vzc2lvbikpICU+JQogICAgbXV0YXRlKGhvc3RHcm91cD0nYWxsJyksCiAgbWV0YS5kYXRhICU+JQogICAgZ3JvdXBfYnkoaG9zdEdyb3VwKSAlPiUKICAgIGZpbHRlcihpcy5uYShsYXRpdHVkZSksIGlzLm5hKGxvbmdpdHVkZSkpJT4lCiAgICBzdW1tYXJpc2Uobj1uX2Rpc3RpbmN0KHJ1bl9hY2Nlc3Npb24pKQopCmBgYAoKCiMgRWRnZSBkaXN0cmlidXRpb25zIChGaWd1cmUgUzQpCmBgYHtyIHBsb3RfZWRnZV9kaXN0cmlidXRpb25zfQphbGwucC5lZGdlcyA8LSBsaXN0KCkKZm9yIChob3N0IGluIGhvc3RzKSB7CiAgCiAgaG9zdE1hdCA8LSBhbGwubWF0cmljZXNbW2hvc3RdXQogIGlmIChob3N0ID09ICdjYW5pc19sdXB1cycpIHsKICAgIGhvc3QgPSAnZG9nJwogIH0gZWxzZSBpZiAoaG9zdCA9PSAnaG9tb19zYXBpZW5zJykgewogICAgaG9zdCA9ICdodW1hbicKICB9CiAgaCA8LSBzdHJfdG9fdGl0bGUoaG9zdCkKICAKICBwLmVkZ2VzIDwtIGhvc3RNYXQgJT4lCiAgICBtdXRhdGUoCiAgICAgIFNlbGVjdGVkID0gY2FzZV93aGVuKAogICAgICAgIHBkIDwgYWxwaGEgJiBjb3JyID49IG1pbl9jb3JyIH4gVFJVRSwKICAgICAgICBUUlVFIH4gRkFMU0UKICAgICAgKQogICAgKSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX3BvaW50KGFlcyh4PWNvcnIsIHk9cGQsIGNvbG9yPVNlbGVjdGVkKSwgYWxwaGE9LjUsIHNpemU9LjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiRkFMU0UiID0gJ3JlZCcsICJUUlVFIiA9ICdibHVlJykpICsKICAgIHRoZW1lX2xpZ2h0KCkgKwogICAgeWxhYignT25lLXRhaWxlZCBwLXZhbHVlJykgKwogICAgeGxhYignQ29ycmVsYXRpb24nKSArCiAgICBnZ3RpdGxlKGgpCiAgCiAgIGFsbC5wLmVkZ2VzIDwtIGMoYWxsLnAuZWRnZXMsIHNldE5hbWVzKGxpc3QocC5lZGdlcyksIGhvc3QpKQp9CgpwLmFsbC5lZGdlcyA8LSBnZ2FycmFuZ2UocGxvdGxpc3QgPSBhbGwucC5lZGdlcywgY29tbW9uLmxlZ2VuZCA9IFQsIG5jb2wgPSAzLCBucm93PTQpCmdnc2F2ZShwbG90ID0gcC5hbGwuZWRnZXMsIGZpbGVuYW1lID0gJ2ZpZ3MvYWxsX2VkZ2VzX2Rpc3QucG5nJywgd2lkdGg9MTAsIGhlaWdodD0xMCkKZ2dzYXZlKHBsb3QgPSBwLmFsbC5lZGdlcywgZmlsZW5hbWUgPSAnZmlncy9hbGxfZWRnZXNfZGlzdC5wZGYnLCB3aWR0aD0xMCwgaGVpZ2h0PTEwKQpnZ3NhdmUocGxvdCA9IHAuYWxsLmVkZ2VzLCBmaWxlbmFtZSA9ICdmaWdzL2FsbF9lZGdlc19kaXN0LmVwcycsIHdpZHRoPTEwLCBoZWlnaHQ9MTApCgpgYGAKCgojIE5ldHdvcmsgdmlzdWFsaXphdGlvbnMgKEZpZ3VyZSAyYSkKRWFjaCBuZXR3b3JrIGlzIHZpc3VhbGl6ZWQgYnkgdXNpbmcgdGhlIEZydWNodGVybWFuLVJlaW5nb2xkIGxheW91dCBhbmQgc2F2ZSBhcyBhIHBkZi9wbmcvdGlmZi4gCmBgYHtyIHBsb3RfbmV0d29ya3MsIHdhcm5pbmc9RkFMU0V9CnNvdXJjZS5uZXR3b3JrLnBsb3RzIDwtIGxpc3QoKQphbGwubmV0d29yayA8LSBOVUxMCmZvciAoaG9zdCBpbiBuYW1lcyhuZXR3b3JrcykpIHsKCiAgRyA8LSBuZXR3b3Jrc1tbaG9zdF1dICAKCiAgaWYoaG9zdCA9PSAnaG9tb19zYXBpZW5zJykgewogICAgaG9zdCA9ICdodW1hbicKICB9IGVsc2UgaWYgKGhvc3QgPT0gJ2NhbmlzX2x1cHVzJykgewogICAgaG9zdCA9ICdkb2cnCiAgfQogIHBsb3R0aXRsZSA8LSBzdHJfdG9fdGl0bGUoc3RyX3JlcGxhY2UoaG9zdCwgJ18nLCAnICcpKQogIAogIAogICMgZGVmaW5lIGxheW91dAogIGUgPC0gZ2V0LmVkZ2VsaXN0KEcsIG5hbWVzPUYpCiAgbGF5b3V0IDwtIHFncmFwaC5sYXlvdXQuZnJ1Y2h0ZXJtYW5yZWluZ29sZChlLCB2Y291bnQgPSB2Y291bnQoRyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhID0gNioodmNvdW50KEcpXjIpLCByZXB1bHNlLnJhZCA9IHZjb3VudChHKV4yLjUpCgogICMgIG1ha2UgdmlzdWFsaXphdGlvbnMgd2l0aCBhbmQgd2l0aG91dCBsZWdlbmRzCiAgZ3JhcGgucGxvdC5sZWcgPC0gZ2dyYXBoKEcsIGxheW91dD0ibWFudWFsIiwgeD1sYXlvdXRbLDFdLCB5PWxheW91dFssMl0pICsKICAgIGdlb21fZWRnZV9saW5rMChhZXMoY29sb3I9Y29yciwgd2lkdGg9Y29yciwgYWxwaGE9Y29ycikpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3I9UmVzRmluZGVyX2NsYXNzKSkgKwogICAgc2NhbGVfZWRnZV9jb2xvcl92aXJpZGlzKCJDb3JyZWxhdGlvbiIsZGlyZWN0aW9uPS0xLCBsaW1pdHM9YygwLjYsMSkpICsKICAgIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2U9YyguMiwgMSksIGd1aWRlPSJub25lIikgKwogICAgc2NhbGVfZWRnZV9hbHBoYShyYW5nZT1jKDAuNSwgMC45KSwgZ3VpZGU9Im5vbmUiKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwoIlJlc0ZpbmRlciBjbGFzcyIsIHZhbHVlcz1jbGFzc2VzX2NvbG9ycykgKwogICAgIyBzY2FsZV9zaGFwZV9tYW51YWwoIkNsYXNzaWZpZWRcbmFzIENJQSIsIHZhbHVlcz1jKCcwJyA9IDE1LCAnMSc9MTcpKSArCiAgICBnZ3RpdGxlKHBsb3R0aXRsZSkgKwogICAgdGhlbWVfZ3JhcGgoKQogIAogIGdnc2F2ZShwbG90PWdyYXBoLnBsb3QubGVnLCBmaWxlbmFtZT1wYXN0ZTAoJ2ZpZ3MvbmV0d29ya18nLCBob3N0LCAnLnBuZycpLCB3aWR0aD0xNSwgaGVpZ2h0PTEwKQoKCgogIGdyYXBoLnBsb3Qubm9sZWcgPC0gZ2dyYXBoKEcsIGxheW91dD0ibWFudWFsIiwgeD1sYXlvdXRbLDFdLCB5PWxheW91dFssMl0pICsKICAgIGdlb21fZWRnZV9saW5rMChhZXMoY29sb3I9Y29yciwgd2lkdGg9Y29yciwgYWxwaGE9Y29ycikpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3I9UmVzRmluZGVyX2NsYXNzKSkgKwogICAgc2NhbGVfZWRnZV9jb2xvcl92aXJpZGlzKGRpcmVjdGlvbj0tMSwgZ3VpZGU9Im5vbmUiLCBsaW1pdHM9YygwLjYsIDEpKSArCiAgICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlPWMoLjEsIDEpLCBndWlkZT0ibm9uZSIpICsKICAgIHNjYWxlX2VkZ2VfYWxwaGEocmFuZ2U9YygwLjUsIDAuOSksIGd1aWRlPSJub25lIikgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jbGFzc2VzX2NvbG9ycywgZ3VpZGU9Im5vbmUiKSArCiAgICBnZ3RpdGxlKHBsb3R0aXRsZSkgKwogICAgdGhlbWVfZ3JhcGgodGl0bGVfc2l6ZSA9IDE0KQogIGdnc2F2ZShwbG90PWdyYXBoLnBsb3Qubm9sZWcsIGZpbGVuYW1lPXBhc3RlMCgnZmlncy9uZXR3b3JrXycsIGhvc3QsICdfbm9sZWcucG5nJyksIHdpZHRoPTEwLCBoZWlnaHQ9MTApCiAgCiAgZ3JhcGgucGxvdC5ub2xlZy5jaWEgPC0gZ2dyYXBoKEcsIGxheW91dD0ibWFudWFsIiwgeD1sYXlvdXRbLDFdLCB5PWxheW91dFssMl0pICsKICAgIGdlb21fZWRnZV9saW5rMChhZXMoY29sb3I9Y29yciwgd2lkdGg9Y29yciwgYWxwaGE9Y29ycikpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3I9UmVzRmluZGVyX2NsYXNzKSkgKwogICAgc2NhbGVfZWRnZV9jb2xvcl92aXJpZGlzKGRpcmVjdGlvbj0tMSwgbGltaXRzPWMoMC42LDEpLCBndWlkZT0ibm9uZSIpICsKICAgIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2U9YyguMiwgMSksIGd1aWRlPSJub25lIikgKwogICAgc2NhbGVfZWRnZV9hbHBoYShyYW5nZT1jKDAuNSwgMC45KSwgZ3VpZGU9Im5vbmUiKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNsYXNzZXNfY29sb3JzLCBndWlkZT0ibm9uZSIpICsKICAgICMgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKCcwJyA9IDE1LCAnMSc9MTcpLCBndWlkZT0ibm9uZSIpICsKICAgIGdndGl0bGUocGxvdHRpdGxlKSArCiAgICB0aGVtZV9ncmFwaCgpCiAgZ2dzYXZlKHBsb3Q9Z3JhcGgucGxvdC5ub2xlZy5jaWEsIGZpbGVuYW1lPXBhc3RlMCgnZmlncy9uZXR3b3JrXycsIGhvc3QsICdfbm9sZWdfY2lhLnBuZycpLCB3aWR0aD0xMCwgaGVpZ2h0PTEwKQoKICAKICAjIHdpdGggYXJnIG5hbWVzIG9uIGl0CiAgZ3JhcGgucGxvdC5hbm5vIDwtIEcgJT4lCiAgICBhY3RpdmF0ZShub2RlcykgJT4lCiAgICBtdXRhdGUobmFtZTIgPSBzdWIoJ19bXl9dKyQnLCAnJywgbmFtZSkpICU+JQogICBnZ3JhcGgobGF5b3V0PSJtYW51YWwiLCB4PWxheW91dFssMV0sIHk9bGF5b3V0WywyXSkgKwogICAgZ2VvbV9lZGdlX2xpbmswKGFlcyhjb2xvcj1jb3JyLCB3aWR0aD1jb3JyLCBhbHBoYT1jb3JyKSkgKwogICAgZ2VvbV9ub2RlX3BvaW50KGFlcyhjb2xvcj1SZXNGaW5kZXJfY2xhc3MpKSArCiAgICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWw9bmFtZTIpLCBzaXplPTIsIHJlcGVsID0gVCwgY2hlY2tfb3ZlcmxhcCA9IFQpICsKICAgIHNjYWxlX2VkZ2VfY29sb3JfdmlyaWRpcygiQ29ycmVsYXRpb24iLGRpcmVjdGlvbj0tMSwgbGltaXRzPWMoMC42LDEpKSArCiAgICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlPWMoLjIsIDEpLCBndWlkZT0ibm9uZSIpICsKICAgIHNjYWxlX2VkZ2VfYWxwaGEocmFuZ2U9YygwLjcsIDAuOSksIGd1aWRlPSJub25lIikgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKCJSZXNGaW5kZXIgY2xhc3MiLCB2YWx1ZXM9Y2xhc3Nlc19jb2xvcnMpICsKICAgIGdndGl0bGUocGxvdHRpdGxlKSArCiAgICB0aGVtZV9ncmFwaCh0aXRsZV9zaXplID0gMTQpCiAgZ2dzYXZlKHBsb3Q9Z3JhcGgucGxvdC5hbm5vLCBmaWxlbmFtZT1wYXN0ZTAoJ2ZpZ3MvbmV0d29ya18nLCBob3N0LCAnX2Fubm8ucG5nJyksIHdpZHRoPTE1LCBoZWlnaHQ9MTApCgogIGlmKGhvc3QgPT0gJ2FsbCcpIHsKICAgIGFsbC5uZXR3b3JrIDwtIGdyYXBoLnBsb3QubGVnCiAgfSBlbHNlIHsKICAgIHNvdXJjZS5uZXR3b3JrLnBsb3RzIDwtIGMoc291cmNlLm5ldHdvcmsucGxvdHMsIHNldE5hbWVzKGxpc3QoZ3JhcGgucGxvdC5ub2xlZy5jaWEpLCBob3N0KSkKICB9Cn0KCnAuc291cmNlcyA8LSBnZ2FycmFuZ2UocGxvdGxpc3Q9c291cmNlLm5ldHdvcmsucGxvdHNbb3JkZXIobmFtZXMoc291cmNlLm5ldHdvcmsucGxvdHMpKV0sIG5yb3c9MiwgbmNvbD1jZWlsaW5nKGxlbmd0aChzb3VyY2UubmV0d29yay5wbG90cykvMiksIGNvbW1vbi5sZWdlbmQgPSBUKQpwLmNvbWJpbmVkIDwtIGdnYXJyYW5nZShhbGwubmV0d29yaywgcC5zb3VyY2VzLCBuY29sPTIsIGNvbW1vbi5sZWdlbmQgPSBULCBsZWdlbmQ9J2JvdHRvbScsIHdpZHRocz1jKC41LCAxKSwgbGFiZWxzPSdhJykKZ2dzYXZlKHBsb3Q9cC5jb21iaW5lZCwgZmlsZW5hbWU9J2ZpZ3MvbmV0d29ya19jb21iaW5lZC5wbmcnLCB3aWR0aD0yMiwgaGVpZ2h0PTgpCgpgYGAKCiMjIE5ldHdvcmsgY2hhcmFjdGVyaXN0aWNzIChGaWd1cmUgMmIpClN1bW1hcmlzZSBnbG9iYWwgcHJvcGVydGllcyBvZiB0aGUgZGlmZmVyZW50IG5ldHdvcmtzCmBgYHtyIG1ha2VfbmV0d29ya19jaGFyYWN0ZXJpc3RpY3N9Cm5ldHdvcmsuY2hhcmFjdGVyaXN0aWNzIDwtIHRpYmJsZSgpCmZvciAoaG9zdCBpbiBuYW1lcyhuZXR3b3JrcykpIHsKICAKICBHIDwtIG5ldHdvcmtzW1tob3N0XV0gI2ltcG9ydEdyYXBoKG5ldEZpbGUpCiAgCiAgbmV0LmNoYXJhY3RlcmlzdGljcyA8LSBzdW1tYXJpc2UuZ3JhcGgoRykgJT4lCiAgICBtdXRhdGUoaG9zdD1ob3N0LCBzb3J0X29yZGVyPXdoaWNoKGhvc3RzID09IGhvc3QpKQogIG5ldHdvcmsuY2hhcmFjdGVyaXN0aWNzIDwtIHJiaW5kKG5ldHdvcmsuY2hhcmFjdGVyaXN0aWNzLCBuZXQuY2hhcmFjdGVyaXN0aWNzKQogIAp9CgpuZXR3b3JrLmNoYXJhY3RlcmlzdGljcyA8LSBuZXR3b3JrLmNoYXJhY3RlcmlzdGljcyAlPiUKICBtdXRhdGUoCiAgICBob3N0ID0gc3RyX3RvX3RpdGxlKGNhc2Vfd2hlbigKICAgICAgaG9zdCA9PSAnY2FuaXNfbHVwdXMnIH4gJ0RvZycsCiAgICAgIGhvc3QgPT0gJ2hvbW9fc2FwaWVucycgfiAnSHVtYW4nLAogICAgICBUUlVFIH4gaG9zdAogICAgKSkKKQogICAgICAKbmV0d29yay5jaGFyYWN0ZXJpc3RpY3MuZmluYWwgPC0gbmV0d29yay5jaGFyYWN0ZXJpc3RpY3MgJT4lCiAgbXV0YXRlKAogICAgZ2xvYmFsX2NsdXN0ZXJpbmdfY29lZmZpY2llbnQgPSByb3VuZChnbG9iYWxfY2x1c3RlcmluZ19jb2VmZmljaWVudCwgMyksCiAgICBlZGdlX2RlbnNpdHkgPSByb3VuZChlZGdlX2RlbnNpdHksIDMpLAogICAgbmV0d29ya19kZW5zaXR5ID0gcm91bmQobmV0d29ya19kZW5zaXR5LDMpCiAgICApICU+JQogIGFycmFuZ2Uoc29ydF9vcmRlcikgJT4lCiAgc2VsZWN0KGhvc3QsIG51bWJlcl9vZl9ub2RlcywgbnVtYmVyX29mX2VkZ2VzLCBnbG9iYWxfY2x1c3RlcmluZ19jb2VmZmljaWVudCwgbmV0d29ya19kZW5zaXR5LCBlZGdlX2RlbnNpdHksIG51bWJlcl9vZl9jb21wb25lbnRzKQoKKHAubmV0Y2hhcnMgPC0gZ2d0ZXh0dGFibGUoCiAgbmV0d29yay5jaGFyYWN0ZXJpc3RpY3MuZmluYWwsIAogIGNvbHMgPSBjKAogICAgc3RyX3dyYXAoJ05ldHdvcmsnLCB3aWR0aD03KSwKICAgIHN0cl93cmFwKCdOby4gbm9kZXMnLCB3aWR0aD03KSwKICAgIHN0cl93cmFwKCdOby4gZWRnZXMnLCB3aWR0aD03KSwKICAgIHN0cl93cmFwKCdHbG9iYWwgY2x1c3RlcmluZyBjb2VmZmljaWVudCcsIHdpZHRoPTE3KSwKICAgIHN0cl93cmFwKCdOZXR3b3JrIGRlbnNpdHknLCB3aWR0aD03KSwKICAgIHN0cl93cmFwKCdFZGdlIGRlbnNpdHknLCB3aWR0aD03KSwKICAgIHN0cl93cmFwKCdOby4gY29tcG9uZW50cycsIHdpZHRoPTcpCiAgKSwKICByb3dzPXJlcCgnJywgbnJvdyhuZXR3b3JrLmNoYXJhY3RlcmlzdGljcy5maW5hbCkpLAogIHRoZW1lID0gdHRoZW1lKGJhc2Vfc3R5bGU9ImxpZ2h0IiwgcGFkZGluZz11bml0KGMoMiwyKSwgIm1tIikpCikpCgpnZ3NhdmUoJ2ZpZ3MvbmV0d29ya19tZXRyaWNzLnBuZycpCmdnc2F2ZSgnZmlncy9uZXR3b3JrX21ldHJpY3MucGRmJykKYGBgCiMjIyBEaXN0cmlidXRpb24gb2YgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIC8gd2VpZ2h0cyAoRmlndXJlIDJjKQpgYGB7ciBwbG90X2NvcnJfZGlzdHMsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTEwfQphbGwuZWRnZXMgPC0gdGliYmxlKCkKZm9yIChob3N0IGluIG5hbWVzKG5ldHdvcmtzKSkgewogIAogIEcgPC0gbmV0d29ya3NbW2hvc3RdXSAKICAKICBob3N0X29yZGVyID0gd2hpY2goaG9zdCA9PSBob3N0cykKICAKICBlZGdlLmRhdGEgPC0gRyAlPiUKICAgIGFzX2RhdGFfZnJhbWUgJT4lCiAgICBzZWxlY3QoZnJvbSwgdG8sIGNvcnIpICU+JQogICAgbXV0YXRlKGhvc3Q9aG9zdCwgaG9zdF9vcmRlcj1ob3N0X29yZGVyKQogIGFsbC5lZGdlcyA8LSByYmluZChhbGwuZWRnZXMsIGVkZ2UuZGF0YSkKfQoKCmFsbC5lZGdlcyA8LSBhbGwuZWRnZXMgJT4lCiAgbXV0YXRlKAogICAgaG9zdCA9IHN0cl90b190aXRsZShjYXNlX3doZW4oCiAgICAgIGhvc3QgPT0gJ2NhbmlzX2x1cHVzJyB+ICdEb2cnLAogICAgICBob3N0ID09ICdob21vX3NhcGllbnMnIH4gJ0h1bWFuJywKICAgICAgVFJVRSB+IGhvc3QKICAgICkpCiAgKQoKKHAuZWRnZS5kaXN0IDwtIGdncGxvdChhbGwuZWRnZXMpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeD1jb3JyKSwgY29sb3I9J3doaXRlJywgYmlud2lkdGggPSAwLjAxKSArCiAgZmFjZXRfZ3JpZChmY3RfcmVvcmRlcihob3N0LCBob3N0X29yZGVyKX4uLCBzY2FsZXM9J2ZyZWVfeScpICsKICB4bGFiKCdDb3JyZWxhdGlvbicpICsgeWxhYignQ291bnQnKSArIAogICN0aGVtZV9wdWJyKCkgCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgCikKICAKYGBgCgoKIyMjIE92ZXJsYXBwaW5nIG5vZGVzIGFuZCBlZGdlcyBpbiB0aGUgbmV0d29ya3MgKEZpZ3VyZSAyZCkKYGBge3IgcGxvdF9uZXR3b3JrX292ZXJsYXBzfQpvdmVybGFwcGluZy5lZGdlcyA8LSBtYXRyaXgobnJvdz1sZW5ndGgobmV0d29ya3MpLCBuY29sPWxlbmd0aChuZXR3b3JrcykpCm92ZXJsYXBwaW5nLm5vZGVzIDwtIG1hdHJpeChucm93PWxlbmd0aChuZXR3b3JrcyksIG5jb2w9bGVuZ3RoKG5ldHdvcmtzKSkKCmNvbG5hbWVzKG92ZXJsYXBwaW5nLmVkZ2VzKSA8LSByb3duYW1lcyhvdmVybGFwcGluZy5lZGdlcykgPC0gbmFtZXMobmV0d29ya3MpCmNvbG5hbWVzKG92ZXJsYXBwaW5nLm5vZGVzKSA8LSByb3duYW1lcyhvdmVybGFwcGluZy5ub2RlcykgPC0gbmFtZXMobmV0d29ya3MpCgpmb3IgKGhvc3RTZXQgaW4gdXRpbHM6OmNvbWJuKG5hbWVzKG5ldHdvcmtzKSwgbT0yLCBzaW1wbGlmeSA9IEYpKSB7CiAgaDEgPSBob3N0U2V0WzFdCiAgaDIgPSBob3N0U2V0WzJdCiAgCiAgRzEgPC0gbmV0d29ya3NbW2gxXV0KICBHMiA8LSBuZXR3b3Jrc1tbaDJdXQogIAogIGlmIChhbnkoaXMubnVsbChHMSksIGlzLm51bGwoRzIpKSkgewogICAgbmV4dAogIH0KICAKICBvdmVybGFwcGluZy5lZGdlc1toMSwgaDJdIDwtIGxlbmd0aChpbnRlcnNlY3QoRShHMSksIEUoRzIpKSkKICBvdmVybGFwcGluZy5ub2Rlc1toMSwgaDJdIDwtIGxlbmd0aCggaW50ZXJzZWN0KFYoRzEpLCBWKEcyKSkpCgp9CgpvdmVybGFwcGluZy5lZGdlcyA8LSBhc190aWJibGUob3ZlcmxhcHBpbmcuZWRnZXMscm93bmFtZXMgPSAnaDEnKSAlPiUKICBwaXZvdF9sb25nZXIoLWgxLCBuYW1lc190byA9ICdoMicpCm92ZXJsYXBwaW5nLm5vZGVzIDwtIGFzX3RpYmJsZShvdmVybGFwcGluZy5ub2Rlcyxyb3duYW1lcyA9ICdoMScpICU+JQogIHBpdm90X2xvbmdlcigtaDEsIG5hbWVzX3RvID0gJ2gyJykgCgoocC5vdmVybGFwcyA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV90aWxlKGRhdGE9b3ZlcmxhcHBpbmcubm9kZXMsIGFlcyh4PWgxLCB5PWgyLCBmaWxsPXZhbHVlKSkgKwogIGdlb21fdGV4dChkYXRhPW92ZXJsYXBwaW5nLm5vZGVzLCBhZXMoeD1oMSwgeT1oMiwgbGFiZWw9dmFsdWUpLCBjb2xvcj0nd2hpdGUnKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Moc3RyX3dyYXAoIk92ZXJsYXBwaW5nIG5vZGVzIiwgMTEpLCBuYS52YWx1ZT1OQSkgKwogIG5ld19zY2FsZV9maWxsKCkgKwogIGdlb21fdGlsZShkYXRhPW92ZXJsYXBwaW5nLmVkZ2VzLCBhZXMoeT1oMSwgeD1oMiwgZmlsbD12YWx1ZSkpICsKICBnZW9tX3RleHQoZGF0YT1vdmVybGFwcGluZy5lZGdlcywgYWVzKHk9aDEsIHg9aDIsIGxhYmVsPXZhbHVlKSwgY29sb3I9J3doaXRlJykgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKHN0cl93cmFwKCJPdmVybGFwcGluZyBlZGdlcyIsIDExKSwgbmEudmFsdWU9TkEsIG9wdGlvbj0nSCcpICsKICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscz1jKCdBaXInLCAnQWxsJywgJ0RvZycsICdDaGlja2VuJywgJ0NvdycsICdGcmVzaHdhdGVyJywgJ0h1bWFuJywgJ01hcmluZScsICdNb3VzZScsICdQaWcnLCAnU29pbCcpKSArCiAgc2NhbGVfeV9kaXNjcmV0ZShsYWJlbHM9YygnQWlyJywgJ0FsbCcsICdEb2cnLCAnQ2hpY2tlbicsICdDb3cnLCAnRnJlc2h3YXRlcicsICdIdW1hbicsICdNYXJpbmUnLCAnTW91c2UnLCAnUGlnJywgJ1NvaWwnKSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLm1hcmdpbj1tYXJnaW4oMCwwLDAsMCkKICApCikKCmdnc2F2ZSgnZmlncy9uZXR3b3JrX292ZXJsYXBzLnBuZycpCmdnc2F2ZSgnZmlncy9uZXR3b3JrX292ZXJsYXBzLnBkZicsIHdpZHRoPTEwLCBoZWlnaHQ9NSkKYGBgCgpjb21iaW5lCmBgYHtyIHBsb3RfY29tYmlfbmV0X2NoYXJzfQpwLm5ldF9kZXNjcyA8LSBnZ2FycmFuZ2UocC5uZXRjaGFycywgcC5lZGdlLmRpc3QsIHAub3ZlcmxhcHMsIG5jb2w9MywgbGFiZWxzPWMoJ2InLCAnYycsICdkJykpCmdnc2F2ZShwbG90PXAubmV0X2Rlc2NzLCBmaWxlbmFtZT0nZmlncy9uZXR3b3JrX2NoYXJhY3RlcmlzdGljcy5wbmcnLCB3aWR0aD0yMCwgaGVpZ2h0PTYpCmBgYAoKIyBMb2NhbCBwcm9wZXJ0aWVzIG9mIG5vZGVzIChGaWd1cmUgMykKbG9va2luZyBhdCB0aGUgbm9kZXMgd2l0aCB0aGUgaGlnaGVzdCBkZWdyZWVzCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTEwfQpub2RlLmNlbnRyYWxpdGllcyA8LSB0aWJibGUoKQpmb3IgKGhvc3QgaW4gbmFtZXMobmV0d29ya3MpKSB7CiAgCiAgRyA8LSBuZXR3b3Jrc1tbaG9zdF1dCiAgCiAgbm9kZS5jZW4gPC0gRyAlPiUKICAgIGFjdGl2YXRlKG5vZGVzKSAlPiUKICAgIG11dGF0ZSgKICAgICAgZGVncmVlID0gY2VudHJhbGl0eV9kZWdyZWUod2VpZ2h0cz1jb3JyKSwKICAgICAgYmV0d2Vlbm5lc3MgPSBjZW50cmFsaXR5X2JldHdlZW5uZXNzKHdlaWdodHM9Y29ycikKICAgICkgJT4lCiAgICBhc190aWJibGUoKSAlPiUKICAgIGZpbHRlcihSZXNGaW5kZXJfY2xhc3MgIT0gJ01pc3NpbmcnKSAlPiUKICAgICN0b3BfbigxMCwgd3Q9ZGVncmVlKSAlPiUKICAgIG11dGF0ZSgKICAgICAgaG9zdD1ob3N0CiAgICApICU+JQogICAgc2VsZWN0KC1jKHgsIHksIHBhZ2VyYW5rLCBub2RlZGVncmVlLCBpZCkpCiAgCiAgbm9kZS5jZW50cmFsaXRpZXMgPC0gcmJpbmQobm9kZS5jZW50cmFsaXRpZXMsIG5vZGUuY2VuKQp9Cgpjb2xfc2NhbGUgPC0gc2NhbGVfY29sb3VyX21hbnVhbChuYW1lID0gIlJlc0ZpbmRlciBjbGFzcyIsIHZhbHVlcyA9IGNsYXNzZXNfY29sb3JzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBmb3JjZSkKCm5vZGUuY2VudHJhbGl0aWVzICU+JQogIGdyb3VwX2J5KGhvc3QpICU+JQogIHRvcF9uKDEwLCB3dD1kZWdyZWUpICU+JQogIG11dGF0ZShuYW1lMiA9IHN1YignX1teX10rJCcsICcnLCBuYW1lKSkgJT4lCiAgbGVmdF9qb2luKGdlbmUucmVscywgYnk9YygibmFtZSIgPSAicmVmU2VxdWVuY2UiLCAiaG9zdCIgPSAiaG9zdEdyb3VwIikpICAlPiUKICBtdXRhdGUoCiAgICBob3N0ID0gc3RyX3RvX3RpdGxlKGNhc2Vfd2hlbigKICAgICAgaG9zdCA9PSAnY2FuaXNfbHVwdXMnIH4gJ0RvZycsCiAgICAgIGhvc3QgPT0gJ2hvbW9fc2FwaWVucycgfiAnSHVtYW4nLAogICAgICBUUlVFIH4gaG9zdAogICAgKSkKICApICU+JQogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4PWhvc3QsIHk9bmFtZTIsIGNvbG9yPVJlc0ZpbmRlcl9jbGFzcywgc2l6ZT1mcmFnQ2xvLCBhbHBoYT1kZWdyZWUpKSAgKwogIHNjYWxlX2FscGhhX2NvbnRpbnVvdXMoIk51bWJlciBvZiBlZGdlcyIsIHJhbmdlPWMoMC4yNSwgMC45NSksIHRyYW5zPSdsb2cnLCBicmVha3M9YygxLCAxMCwgMjUsIDUwKSkgKwogIHNjYWxlX3NpemVfY29udGludW91cygiUmVsYXRpdmUgYWJ1bmRhbmNlIiwgcmFuZ2U9YygyLCA4KSwgYnJlYWtzPWMoNSwgMTAsIDI1LCA1MCwgNzUsIDEwMCkpICsKICBjb2xfc2NhbGUgKwogIHRoZW1lX2xpZ2h0KCkgKwogICMgIHRoZW1lKAogICMgIGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nLCAKICAjICBsZWdlbmQuYm94PSJ2ZXJ0aWNhbCIKICAjKSArCiAgZ3VpZGVzKAogICAgY29sb3I9Z3VpZGVfbGVnZW5kKGJ5cm93ID0gRiwgbmNvbD0xLCB0aXRsZS5wb3NpdGlvbj0idG9wIikKICAgICNzaXplPWd1aWRlX2xlZ2VuZCh0aXRsZS5wb3NpdGlvbj0idG9wIikKICApICsKICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICBnZ3RpdGxlKCJBUkdzIHdpdGggMTAgaGlnaGVzdCBudW1iZXIgb2YgZWRnZXMgcGVyIG5ldHdvcmsiKQoKZ2dzYXZlKCdmaWdzL2RlZ3JlZV90b3AxMF9uZXR3b3JrX2FidW5kYW5jZXNfcmVsLnBuZycsIHdpZHRoID0gMTUsIGhlaWdodD0xMCkKZ2dzYXZlKCdmaWdzL2RlZ3JlZV90b3AxMF9uZXR3b3JrX2FidW5kYW5jZXNfcmVsLnBkZicsIHdpZHRoID0gMTUsIGhlaWdodD0xMCkKZ2dzYXZlKCdmaWdzL2RlZ3JlZV90b3AxMF9uZXR3b3JrX2FidW5kYW5jZXNfcmVsLmVwcycsIHdpZHRoID0gMTUsIGhlaWdodD0xMCkKYGBgCiMjIFNlbGVjdGVkIGdlbmVzIGluIG5ldHdvcmtzIChGaWd1cmUgUzUpCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmdlbmUucmVnZXhlcyA8LSBjKCJjYXRBMSIsICJtZWZcXChBXFwpXzEiLCAidGV0XFwoTFxcKV80IikKI2FsbC5nZW5lLmdyYXBocGxvdHMgPC0gbGlzdCgpCmdlbmUuZ3JhcGhwbG90cyA8LSBsaXN0KCkKZm9yIChnZW5lLnJlZ2V4IGluIGdlbmUucmVnZXhlcykgewogIAogIGFkZF9sZWdlbmQgPSBsZW5ndGgoZ2VuZS5yZWdleGVzKSA9PSB3aGljaChnZW5lLnJlZ2V4ID09IGdlbmUucmVnZXhlcykKICBmb3IgKGhvc3QgaW4gaG9zdHMpIHsKICAgIEcgPC0gbmV0d29ya3NbW2hvc3RdXQogICAgRy5zZWwgPC0gZXh0cmFjdF9uZWlnaGJvcmhvb2QoZ2VuZS5yZWdleCwgRykKICAgIGlmIChpcy5udWxsKEcuc2VsKSkgewogICAgICBwLmggPC0gZ2dwbG90KCkgKyBnZ3RpdGxlKHN0cl90b190aXRsZShob3N0KSkgKyB0aGVtZV92b2lkKCkgKyB0aGVtZV9ncmFwaChiYXNlX2ZhbWlseT0ic2FucyIpICsgYW5ub3RhdGUoInRleHQiLGxhYmVsPSJObyBjb3JyZWxhdGlvbnMiLCB4PTAsIHk9MCkKICAgIH0gZWxzZSB7CiAgICAgIAogICAgICBwLmggPC0gRy5zZWwgJT4lCiAgICAgICAgYWN0aXZhdGUobm9kZXMpICU+JQogICAgICAgIGxlZnRfam9pbihmaWx0ZXIoZ2VuZS5yZWxzLCBob3N0R3JvdXAgPT0gaG9zdCksIGJ5PWMoIm5hbWUiID0gInJlZlNlcXVlbmNlIikpICU+JQogICAgICAgIG11dGF0ZShuYW1lPXN1YignX1teX10rJCcsICcnLCBuYW1lKSkgJT4lCiAgICAgICAgZ2dyYXBoKGxheW91dD0ibmljZWx5IikgKwogICAgICAgIGdlb21fZWRnZV9saW5rMChhZXMoY29sb3I9Y29yciwgd2lkdGg9Y29yciwgYWxwaGE9Y29ycikpICsKICAgICAgICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yPVJlc0ZpbmRlcl9jbGFzcywgc2l6ZT1mcmFnQ2xvLCBzaGFwZT1zZWwpKSAgKwogICAgICAgIGdlb21fbm9kZV9sYWJlbChhZXMobGFiZWw9bmFtZSksIHNpemU9MiwgcmVwZWwgPSBUKSArCiAgICAgICAgc2NhbGVfZWRnZV93aWR0aChyYW5nZT1jKC4yLCAxKSwgZ3VpZGU9Im5vbmUiKSArCiAgICAgICAgc2NhbGVfZWRnZV9hbHBoYShyYW5nZT1jKDAuNSwgMC45KSwgZ3VpZGU9Im5vbmUiKSArCiAgICAgICAgc2NhbGVfZWRnZV9jb2xvcl92aXJpZGlzKCJDb3JyZWxhdGlvbiIsZGlyZWN0aW9uPS0xLGxpbWl0cz1jKDAuNiwgMSkpICsKICAgICAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMoJ1JlbGF0aXZlIGFidW5kYW5jZScsIHJhbmdlPWMoMSwgNCksICBicmVha3M9Yyg1LCAxMCwgMjUsIDUwLCA3NSwgMTAwKSkgKwogICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCgiUmVzRmluZGVyIGNsYXNzIiwgdmFsdWVzPWNsYXNzZXNfY29sb3JzKSArCiAgICAgICAgc2NhbGVfc2hhcGVfbWFudWFsKCJIaWdobGlnaHRlZCIsIHZhbHVlcz1saXN0KCJZZXMiID0gMTYsICJObyIgPSAxNSkpKwogICAgICAgIGdndGl0bGUoc3RyX3RvX3RpdGxlKGhvc3QpKSArCiAgICAgICAgdGhlbWVfZ3JhcGgoYmFzZV9mYW1pbHk9InNhbnMiKSAKICAgICAgCiAgICAgIGlmKCFhZGRfbGVnZW5kKXsKICAgICAgICBwLmggPC0gcC5oICsgZ3VpZGVzKGNvbG91cj0ibm9uZSIsIHNoYXBlPSJub25lIiwgc2l6ZT0ibm9uZSIsIGVkZ2VfY29sb3I9Im5vbmUiKQogICAgICB9CiAgICAgICMgaWYgKGFkZF9sZWdlbmQpIHsKICAgICAgIyAgIHAuaCA8LSBwLmggKyAKICAgICAgIyAgICAgc2NhbGVfY29sb3JfbWFudWFsKCJSZXNGaW5kZXIgY2xhc3MiLCB2YWx1ZXM9Y2xhc3Nlc19jb2xvcnMpICsKICAgICAgIyAgICAgc2NhbGVfZWRnZV9jb2xvcl92aXJpZGlzKCJDb3JyZWxhdGlvbiIsZGlyZWN0aW9uPS0xLGxpbWl0cz1jKDAuNiwgMSkpICsKICAgICAgIyAgICAgc2NhbGVfc2l6ZSgnUmVsYXRpdmUgYWJ1bmRhbmNlJywgcmFuZ2U9YygxLCA0KSkgKwogICAgICAjICAgICBzY2FsZV9zaGFwZV9tYW51YWwoIkhpZ2hsaWdodGVkIiwgdmFsdWVzPWxpc3QoIlllcyIgPSAxNiwgIk5vIiA9IDE1KSkgIAogICAgICAjIAogICAgICAjIH0gZWxzZSB7CiAgICAgICMgICBwLmggPC0gcC5oICsgCiAgICAgICMgICAgIHNjYWxlX2NvbG9yX21hbnVhbChndWlkZT0ibm9uZSIsIHZhbHVlcz1jbGFzc2VzX2NvbG9ycykgKwogICAgICAjICAgICBzY2FsZV9lZGdlX2NvbG9yX3ZpcmlkaXMoZ3VpZGU9Im5vbmUiLGRpcmVjdGlvbj0tMSxsaW1pdHM9YygwLjYsIDEpKSArCiAgICAgICMgICAgIHNjYWxlX3NoYXBlX21hbnVhbChndWlkZT0ibm9uZSIsIHZhbHVlcz1saXN0KCJZZXMiID0gMTYsICJObyIgPSAxNSkpICArCiAgICAgICMgICAgIHNjYWxlX3NpemUoZ3VpZGU9Im5vbmUiLCByYW5nZT1jKDEsIDQpKSAKCiAgICAgICMgfQogICAgfQogICAgICBnZW5lLmdyYXBocGxvdHMgPC0gYyhnZW5lLmdyYXBocGxvdHMsIGxpc3QocC5oKSkKICB9CiAgI3ByaW50KHBhc3RlKGdlbmUucmVnZXgsIGxlbmd0aChnZW5lLmdyYXBocGxvdHMpKSkKICAjcC5nIDwtIGdnYXJyYW5nZShwbG90bGlzdD1nZW5lLmdyYXBocGxvdHMsIG5yb3c9MSwgY29tbW9uLmxlZ2VuZCA9IFQsIGxlZ2VuZD0iYm90dG9tIikKICAjYWxsLmdlbmUuZ3JhcGhwbG90cyA8LSBjKGFsbC5nZW5lLmdyYXBocGxvdHMsIGxpc3QocC5nKSkKfQoKcC5ncC5hbGwgPC0gZ2dhcnJhbmdlKAogIHBsb3RsaXN0PWdlbmUuZ3JhcGhwbG90cywKICBuY29sPTExLCBucm93PWxlbmd0aChnZW5lLnJlZ2V4ZXMpLCAKICBjb21tb24ubGVnZW5kID0gVCwgCiAgbGFiZWxzID0gYygiYS4gY2F0QTFfMSIsIHJlcCgiIiwgMTApLCAiYi4gbWVmKEEpXzEiLCByZXAoIiIsIDEwKSwgImMuIHRldChMKV80IiwgcmVwKCIiLCAxMCkpLCAKICBmb250LmxhYmVsID0gbGlzdChzaXplID0gMTgsIGNvbG9yID0gImJsYWNrIiwgZmFjZSA9ICJib2xkIiwgZmFtaWx5ID0gTlVMTCkKKQoKZ2dzYXZlKHBsb3Q9cC5ncC5hbGwsIGZpbGVuYW1lID0gImZpZ3MvZGVncmVlX3RvcDEwX25ldHdvcmtfc2VsLnBuZyIsIHdpZHRoPTMwLGhlaWdodD0xNSkKZ2dzYXZlKHBsb3Q9cC5ncC5hbGwsIGZpbGVuYW1lID0gImZpZ3MvZGVncmVlX3RvcDEwX25ldHdvcmtfc2VsLmVwcyIsIHdpZHRoPTMwLGhlaWdodD0xNSwgZmFtaWx5PSJzYW5zIikKZ2dzYXZlKHBsb3Q9cC5ncC5hbGwsIGZpbGVuYW1lID0gImZpZ3MvZGVncmVlX3RvcDEwX25ldHdvcmtfc2VsLnBkZiIsIHdpZHRoPTMwLGhlaWdodD0xNSkKIyBnZ3NhdmUgZG9lcyBub3Qgd2FudCB0byBjcmVhdGUgcGRmLCBzbyBsZXQncyB1c2UgY29udmVydAojIHN5c3RlbSgiY29udmVydCBvdXRwdXQvbmV0d29ya3Nfc3BhcmNjX2ZyYWc1MF9taW5uMTAvZGVncmVlX3RvcDEwX25ldHdvcmtfc2VsLnBuZyBvdXRwdXQvbmV0d29ya3Nfc3BhcmNjX2ZyYWc1MF9taW5uMTAvZGVncmVlX3RvcDEwX25ldHdvcmtfc2VsLnBkZiIpCmBgYAoKIyBOdW1iZXIgb2Ygbm9kZXMgYW5kIGNvbm5lY3Rpb25zIHRvIHRoZSBkaWZmZXJlbnQgY2xhc3NlcyAgKEZpZ3VyZSA0KQpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0KY2xhc3NEZWdyZWVTdW1zIDwtIHRpYmJsZSgpCgpmb3IgKGhvc3QgaW4gbmFtZXMobmV0d29ya3MpKSB7CiAgRyA8LSBuZXR3b3Jrc1tbaG9zdF1dCiAgCiAgY2xhc3MuZGVncmVlLnN1bXMgPC0gRyAlPiUgCiAgICBhY3RpdmF0ZShub2RlcykgJT4lIAogICAgYXNfdGliYmxlKCkgJT4lIAogICAgZ3JvdXBfYnkoUmVzRmluZGVyX2NsYXNzKSAlPiUKICAgIHN1bW1hcmlzZShzdW1fZGVncmVlID0gc3VtKG5vZGVkZWdyZWUpLCBuX25vZGVzPW5fZGlzdGluY3QobmFtZSkpICU+JQogICAgbXV0YXRlKGhvc3Q9c3RyX3RvX3RpdGxlKHN0cl9yZXBsYWNlKGhvc3QsICdfJywgJyAnKSkpCiAgCiAgY2xhc3NEZWdyZWVTdW1zIDwtIHJiaW5kKGNsYXNzRGVncmVlU3VtcywgY2xhc3MuZGVncmVlLnN1bXMpCn0KCgpjbGFzc0RlZ3JlZVN1bXMgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHg9bl9ub2RlcywgeT1zdW1fZGVncmVlLCBjb2xvcj1SZXNGaW5kZXJfY2xhc3MsIHNoYXBlPWhvc3QpKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKCJOZXR3b3JrIiwgdmFsdWVzPWMoMToxMSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoIlJlc0ZpbmRlciBjbGFzcyIsIHZhbHVlcz1jbGFzc2VzX2NvbG9ycykgICsKICB4bGFiKCdOdW1iZXIgb2Ygbm9kZXMgKGdlbmVzKScpICsKICB5bGFiKCdOdW1iZXIgb2YgZWRnZXMgKGNvcnJlbGF0aW9ucyknKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIGxlZ2VuZC5ib3ggPSAidmVydGljYWwiLCBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAyLCBieXJvdz1GKSkgCmdnc2F2ZSgnZmlncy9jb3VudF9jbGFzc19lZGdlcy5wbmcnLCB3aWR0aD0xMCwgaGVpZ2h0PTEwKQpnZ3NhdmUoJ2ZpZ3MvY291bnRfY2xhc3NfZWRnZXMucGRmJywgd2lkdGg9MTAsIGhlaWdodD0xMCkKZ2dzYXZlKCdmaWdzL2NvdW50X2NsYXNzX2VkZ2VzLmVwcycsIHdpZHRoPTEwLCBoZWlnaHQ9MTApCiMgcCArIG9wdHMobGVnZW5kLmRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIGxlZ2VuZC5ib3ggPSAidmVydGljYWwiKQpgYGAKCiMjIEF2ZXJhZ2luZyBjbGFzcy1jbGFzcyBjb3JyZWxhdGlvbnMgKEZpZ3VyZSBTNikKSG93OiB3aXRoIEZpc2hlcidzIHotdHJhbnNmb3JtYXRpb24uCmBgYHtyIGF2Z19uZXR3b3JrX2NvcnJzLCBmaWcuaGVpZ2h0PTE1LCBmaWcud2lkdGg9MTV9CmNsYXNzMmNsYXNzQ29ycnMgPC0gdGliYmxlKCkKZm9yIChob3N0IGluIG5hbWVzKG5ldHdvcmtzKSkgewogIAogIEcgPC0gbmV0d29ya3NbW2hvc3RdXQogIGhvIDwtIHdoaWNoKGhvc3QgPT0gaG9zdHMpCiAgCiAgRy5kYXRhIDwtIGFzX2RhdGFfZnJhbWUoRykgJT4lCiAgICBsZWZ0X2pvaW4oc2VsZWN0KGFzX2RhdGFfZnJhbWUoRywgd2hhdD0ndmVydGljZXMnKSwgYyhuYW1lLCBSZXNGaW5kZXJfY2xhc3MpKSwgYnk9YygiZnJvbSIgPSAibmFtZSIpKSAlPiUKICAgIHJlbmFtZShWYXIxX2NsYXNzID0gUmVzRmluZGVyX2NsYXNzKSU+JQogICAgbGVmdF9qb2luKHNlbGVjdChhc19kYXRhX2ZyYW1lKEcsIHdoYXQ9J3ZlcnRpY2VzJyksIGMobmFtZSwgUmVzRmluZGVyX2NsYXNzKSksIGJ5PWMoInRvIiA9ICJuYW1lIikpICU+JQogICAgcmVuYW1lKFZhcjJfY2xhc3MgPSBSZXNGaW5kZXJfY2xhc3MpCiAgCiAgYXZnLmNvcnIgPC0gRy5kYXRhICU+JQogICAgbXV0YXRlKHpzY29yZSA9IERlc2NUb29sczo6RmlzaGVyWihjb3JyKSkgJT4lCiAgICBncm91cF9ieShGcm9tPXBtaW4oVmFyMV9jbGFzcywgVmFyMl9jbGFzcyksIFRvPXBtYXgoVmFyMV9jbGFzcywgVmFyMl9jbGFzcykpICU+JQogICAgc3VtbWFyaXNlKG1lYW56c2NvcmUgPSBtZWFuKHpzY29yZSksIG5fZWRnZXMgPSBuKCkpICU+JQogICAgbXV0YXRlKGF2Z2NvcnIgPSBEZXNjVG9vbHM6OkZpc2hlclpJbnYobWVhbnpzY29yZSksIGhvc3Q9aG9zdCwgaG9zdF9vcmRlcj1obykgCiAgCiAgY2xhc3MyY2xhc3NDb3JycyA8LSByYmluZChjbGFzczJjbGFzc0NvcnJzLCBhdmcuY29ycikKfQoKY2xhc3MyY2xhc3NDb3JyczIgPC0gY2xhc3MyY2xhc3NDb3JycyAlPiUKICB1bml0ZShGcm9tLlRvLCBGcm9tLCBUbywgc2VwPSItIiwgcmVtb3ZlPUYpICU+JQogIGdyb3VwX2J5KGhvc3QpICU+JQogIG11dGF0ZShwY3RfZWRnZXMgPSBuX2VkZ2VzIC8gc3VtKG5fZWRnZXMpICogMTAwKSAlPiUKICBtdXRhdGUoCiAgICBob3N0ID0gc3RyX3RvX3RpdGxlKGNhc2Vfd2hlbigKICAgICAgaG9zdCA9PSAnY2FuaXNfbHVwdXMnIH4gJ0RvZycsCiAgICAgIGhvc3QgPT0gJ2hvbW9fc2FwaWVucycgfiAnSHVtYW4nLAogICAgICBUUlVFIH4gaG9zdAogICAgKSkKKQoKcGxvdC5hdmdzLmNvcnJzIDwtIGdncGxvdChjbGFzczJjbGFzc0NvcnJzMikgKwogIGdlb21fcG9pbnQoYWVzKHg9aG9zdCwgeT1Gcm9tLlRvLCBzaXplPXBjdF9lZGdlcywgY29sb3I9YXZnY29ycikpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzKCJBdmVyYWdlXG5jb3JyZWxhdGlvbiIsZGlyZWN0aW9uPS0xLCBsaW1pdHM9YygwLjYsMSkpICsKICBzY2FsZV9zaXplKCJQZXJjZW50YWdlXG5vZiBlZGdlcyIpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKCmF4aXMuY29sb3IuY29ucyA8LSBnZ3Bsb3QoY2xhc3MyY2xhc3NDb3JyczIpICsKICBnZW9tX3RpbGUoYWVzKHg9IkMxIiwgeT1Gcm9tLlRvLCBmaWxsPUZyb20pKSArCiAgZ2VvbV90aWxlKGFlcyh4PSJDMiIsIHk9RnJvbS5UbywgZmlsbD1UbykpICsKICBzY2FsZV9maWxsX21hbnVhbCgiUmVzRmluZGVyIGNsYXNzIix2YWx1ZXM9Y2xhc3Nlc19jb2xvcnMpICsKICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQobmNvbCA9IDQpKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpCiAgKQoKZ2dhcnJhbmdlKAogIGdnYXJyYW5nZShheGlzLmNvbG9yLmNvbnMsIHBsb3QuYXZncy5jb3JycywgbmNvbD0yLCB3aWR0aHM9YygwLjA1NSwgMSksIGNvbW1vbi5sZWdlbmQgPSBUKSwKICBhc19nZ3Bsb3QoZ2V0X2xlZ2VuZChwbG90LmF2Z3MuY29ycnMpKSwgCiAgbmNvbD0yLAogIHdpZHRocyA9IGMoMSwgMC4xKQopCiNnZ3NhdmUoCiMgIGZpbGVuYW1lID0gJ291dHB1dC9uZXR3b3Jrc19zcGFyY2NfZnJhZzUwX21pbm4xMC9uZXR3b3JrX2F2Z19jb3Jycy5wZGYnLAojICB3aWR0aD0xNiwgaGVpZ2h0PTE1CiMpCiNzeXN0ZW0oImNvbnZlcnQgb3V0cHV0L25ldHdvcmtzX3NwYXJjY19mcmFnNTBfbWlubjEwL25ldHdvcmtfYXZnX2NvcnJzLnBkZiBvdXRwdXQvbmV0d29ya3Nfc3BhcmNjX2ZyYWc1MF9taW5uMTAvbmV0d29ya19hdmdfY29ycnMucG5nIikKYGBgCg==